Skip to content

Commit

Permalink
Release 0.15.0 (#123)
Browse files Browse the repository at this point in the history
* Remove python2 code

* Remove python2 code again

* Replace unittest with pytest

* Bump version to 0.15.0

* Blackify
  • Loading branch information
adrienball authored Mar 10, 2020
1 parent 5720213 commit 34a6d0b
Show file tree
Hide file tree
Showing 20 changed files with 1,570 additions and 1,796 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ language: python

matrix:
include:
- python: 2.7
env: TOXENV=py27
- python: 3.5
env: TOXENV=py35
- python: 3.6
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.

## [0.15.0] - 2020-03-10
### Changed
- Remove support of Python2.7 [#123](https://github.com/snipsco/snips-nlu-metrics/pull/123)

## [0.14.6] - 2020-01-14
### Added
- Support for python3.8
- Support for python3.8 [#121](https://github.com/snipsco/snips-nlu-metrics/pull/121)

## [0.14.5] - 2019-08-20
### Fixed
Expand Down Expand Up @@ -60,6 +64,8 @@ All notable changes to this project will be documented in this file.
- Samples


[0.15.0]: https://github.com/snipsco/snips-nlu-metrics/compare/0.14.6...0.15.0
[0.14.6]: https://github.com/snipsco/snips-nlu-metrics/compare/0.14.5...0.14.6
[0.14.5]: https://github.com/snipsco/snips-nlu-metrics/compare/0.14.4...0.14.5
[0.14.4]: https://github.com/snipsco/snips-nlu-metrics/compare/0.14.3...0.14.4
[0.14.3]: https://github.com/snipsco/snips-nlu-metrics/compare/0.14.2...0.14.3
Expand Down
6 changes: 2 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ the ``Engine`` API:

.. code-block:: python
from builtins import object
class Engine(object):
class Engine:
def fit(self, dataset):
# Perform training ...
return self
Expand Down Expand Up @@ -117,7 +115,7 @@ You can also compute metrics on a custom NLU engine, here is a simple example:
from snips_nlu_metrics import compute_train_test_metrics
class MyNLUEngine(object):
class MyNLUEngine:
def fit(self, dataset):
self.intent_list = list(dataset["intents"])
return self
Expand Down
71 changes: 32 additions & 39 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,50 @@
import io
import os
from pathlib import Path

from setuptools import setup, find_packages

packages = [p for p in find_packages() if "tests" not in p]

PACKAGE_NAME = "snips_nlu_metrics"
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
PACKAGE_PATH = os.path.join(ROOT_PATH, PACKAGE_NAME)
README = os.path.join(ROOT_PATH, "README.rst")
ROOT_PATH = Path(__file__).resolve().parent
PACKAGE_PATH = ROOT_PATH / PACKAGE_NAME
README = ROOT_PATH / "README.rst"
VERSION = "__version__"

with io.open(os.path.join(PACKAGE_PATH, VERSION)) as f:
with (PACKAGE_PATH / VERSION).open() as f:
version = f.readline().strip()

with io.open(README, 'rt', encoding='utf8') as f:
with README.open(encoding="utf8") as f:
readme = f.read()

install_requires = [
"future",
"numpy>=1.7,<2.0",
"scipy>=1.0,<2.0",
"scikit-learn>=0.21.0,<0.23; python_version>='3.5'",
"scikit-learn>=0.19,<0.21; python_version<'3.5'",
"joblib>=0.13,<0.15"
"joblib>=0.13,<0.15",
]

extras_require = {
"test": [
"mock>=2.0,<3.0",
]
}

setup(name=PACKAGE_NAME,
description="Python package to compute NLU metrics",
long_description=readme,
version=version,
author="Adrien Ball",
author_email="[email protected]",
license="Apache 2.0",
url="https://github.com/snipsco/snips-nlu-metrics",
classifiers=[
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
keywords="metrics nlu nlp intent slots entity parsing",
extras_require=extras_require,
install_requires=install_requires,
packages=packages,
include_package_data=True,
zip_safe=False)
extras_require = {"test": ["mock>=2.0,<3.0", "pytest>=5.3.1,<6",]}

setup(
name=PACKAGE_NAME,
description="Python package to compute NLU metrics",
long_description=readme,
version=version,
author="Adrien Ball",
author_email="[email protected]",
license="Apache 2.0",
url="https://github.com/snipsco/snips-nlu-metrics",
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
keywords="metrics nlu nlp intent slots entity parsing",
extras_require=extras_require,
install_requires=install_requires,
packages=packages,
include_package_data=True,
zip_safe=False,
)
8 changes: 4 additions & 4 deletions snips_nlu_metrics/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import absolute_import

from snips_nlu_metrics.engine import Engine
from snips_nlu_metrics.metrics import (compute_train_test_metrics,
compute_cross_val_metrics)
from snips_nlu_metrics.metrics import (
compute_train_test_metrics,
compute_cross_val_metrics,
)
2 changes: 1 addition & 1 deletion snips_nlu_metrics/__version__
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.14.6
0.15.0
7 changes: 1 addition & 6 deletions snips_nlu_metrics/engine.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
from __future__ import unicode_literals

from abc import ABCMeta, abstractmethod
from builtins import object

from future.utils import with_metaclass


class Engine(with_metaclass(ABCMeta, object)):
class Engine(metaclass=ABCMeta):
"""Abstract class which represents an engine that can be used in the
metrics API. All engine classes must inherit from `Engine`.
"""
Expand Down
124 changes: 82 additions & 42 deletions snips_nlu_metrics/metrics.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
from __future__ import division, print_function, unicode_literals

import io
import json
import logging
from pathlib import Path

from future.utils import iteritems
from joblib import Parallel, delayed
from past.builtins import basestring

from snips_nlu_metrics.utils.constants import (
AVERAGE_METRICS, CONFUSION_MATRIX, INTENTS, INTENT_UTTERANCES, METRICS,
PARSING_ERRORS, UTTERANCES)
AVERAGE_METRICS,
CONFUSION_MATRIX,
INTENTS,
INTENT_UTTERANCES,
METRICS,
PARSING_ERRORS,
UTTERANCES,
)
from snips_nlu_metrics.utils.exception import NotEnoughDataError
from snips_nlu_metrics.utils.metrics_utils import (
aggregate_matrices, aggregate_metrics, compute_average_metrics,
compute_engine_metrics, compute_precision_recall_f1, compute_split_metrics,
create_shuffle_stratified_splits)
aggregate_matrices,
aggregate_metrics,
compute_average_metrics,
compute_engine_metrics,
compute_precision_recall_f1,
compute_split_metrics,
create_shuffle_stratified_splits,
)

logger = logging.getLogger(__name__)


def compute_cross_val_metrics(
dataset, engine_class, nb_folds=5, train_size_ratio=1.0,
drop_entities=False, include_slot_metrics=True,
slot_matching_lambda=None, progression_handler=None, num_workers=1,
seed=None, out_of_domain_utterances=None, intents_filter=None):
dataset,
engine_class,
nb_folds=5,
train_size_ratio=1.0,
drop_entities=False,
include_slot_metrics=True,
slot_matching_lambda=None,
progression_handler=None,
num_workers=1,
seed=None,
out_of_domain_utterances=None,
intents_filter=None,
):
"""Compute end-to-end metrics on the dataset using cross validation
Args:
Expand Down Expand Up @@ -68,14 +84,20 @@ class must inherit from `Engine`
- "average_metrics": the metrics averaged over all intents
"""

if isinstance(dataset, basestring):
with io.open(dataset, encoding="utf8") as f:
if isinstance(dataset, (str, Path)):
with Path(dataset).open(encoding="utf8") as f:
dataset = json.load(f)

try:
splits = create_shuffle_stratified_splits(
dataset, nb_folds, train_size_ratio, drop_entities,
seed, out_of_domain_utterances, intents_filter)
dataset,
nb_folds,
train_size_ratio,
drop_entities,
seed,
out_of_domain_utterances,
intents_filter,
)
except NotEnoughDataError as e:
logger.warning("Not enough data, skipping metrics computation: %r", e)
return {
Expand All @@ -94,8 +116,13 @@ class must inherit from `Engine`
def compute_metrics(split_):
logger.info("Computing metrics for dataset split ...")
return compute_split_metrics(
engine_class, split_, intent_list, include_slot_metrics,
slot_matching_lambda, intents_filter)
engine_class,
split_,
intent_list,
include_slot_metrics,
slot_matching_lambda,
intents_filter,
)

effective_num_workers = min(num_workers, len(splits))
if effective_num_workers > 1:
Expand All @@ -107,26 +134,28 @@ def compute_metrics(split_):
for result in enumerate(results):
split_index, (split_metrics, errors, confusion_matrix) = result
global_metrics = aggregate_metrics(
global_metrics, split_metrics, include_slot_metrics)
global_metrics, split_metrics, include_slot_metrics
)
global_confusion_matrix = aggregate_matrices(
global_confusion_matrix, confusion_matrix)
global_confusion_matrix, confusion_matrix
)
global_errors += errors
logger.info("Done computing %d/%d splits"
% (split_index + 1, total_splits))
logger.info("Done computing %d/%d splits" % (split_index + 1, total_splits))

if progression_handler is not None:
progression_handler(
float(split_index + 1) / float(total_splits))
progression_handler(float(split_index + 1) / float(total_splits))

global_metrics = compute_precision_recall_f1(global_metrics)

average_metrics = compute_average_metrics(
global_metrics,
ignore_none_intent=True if out_of_domain_utterances is None else False)
ignore_none_intent=True if out_of_domain_utterances is None else False,
)

nb_utterances = {intent: len(data[UTTERANCES])
for intent, data in iteritems(dataset[INTENTS])}
for intent, metrics in iteritems(global_metrics):
nb_utterances = {
intent: len(data[UTTERANCES]) for intent, data in dataset[INTENTS].items()
}
for intent, metrics in global_metrics.items():
metrics[INTENT_UTTERANCES] = nb_utterances.get(intent, 0)

return {
Expand All @@ -138,8 +167,13 @@ def compute_metrics(split_):


def compute_train_test_metrics(
train_dataset, test_dataset, engine_class, include_slot_metrics=True,
slot_matching_lambda=None, intents_filter=None):
train_dataset,
test_dataset,
engine_class,
include_slot_metrics=True,
slot_matching_lambda=None,
intents_filter=None,
):
"""Compute end-to-end metrics on `test_dataset` after having trained on
`train_dataset`
Expand Down Expand Up @@ -171,12 +205,12 @@ class must inherit from `Engine`
- "average_metrics": the metrics averaged over all intents
"""

if isinstance(train_dataset, basestring):
with io.open(train_dataset, encoding="utf8") as f:
if isinstance(train_dataset, (str, Path)):
with Path(train_dataset).open(encoding="utf8") as f:
train_dataset = json.load(f)

if isinstance(test_dataset, basestring):
with io.open(test_dataset, encoding="utf8") as f:
if isinstance(test_dataset, (str, Path)):
with Path(test_dataset).open(encoding="utf8") as f:
test_dataset = json.load(f)

intent_list = set(train_dataset["intents"])
Expand All @@ -188,20 +222,26 @@ class must inherit from `Engine`
engine.fit(train_dataset)
test_utterances = [
(intent_name, utterance)
for intent_name, intent_data in iteritems(test_dataset[INTENTS])
for intent_name, intent_data in test_dataset[INTENTS].items()
for utterance in intent_data[UTTERANCES]
if intents_filter is None or intent_name in intents_filter
]

logger.info("Computing metrics...")
metrics, errors, confusion_matrix = compute_engine_metrics(
engine, test_utterances, intent_list, include_slot_metrics,
slot_matching_lambda, intents_filter)
engine,
test_utterances,
intent_list,
include_slot_metrics,
slot_matching_lambda,
intents_filter,
)
metrics = compute_precision_recall_f1(metrics)
average_metrics = compute_average_metrics(metrics)
nb_utterances = {intent: len(data[UTTERANCES])
for intent, data in iteritems(train_dataset[INTENTS])}
for intent, intent_metrics in iteritems(metrics):
nb_utterances = {
intent: len(data[UTTERANCES]) for intent, data in train_dataset[INTENTS].items()
}
for intent, intent_metrics in metrics.items():
intent_metrics[INTENT_UTTERANCES] = nb_utterances.get(intent, 0)
return {
CONFUSION_MATRIX: confusion_matrix,
Expand Down
Loading

0 comments on commit 34a6d0b

Please sign in to comment.