mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-21 08:06:47 +00:00
186 lines
5.7 KiB
Python
186 lines
5.7 KiB
Python
# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import string
|
|
from typing import List, Optional
|
|
|
|
import frozendict
|
|
|
|
from nemo.collections.common.parts.preprocessing import cleaners
|
|
|
|
|
|
class CharParser:
|
|
"""Functor for parsing raw strings into list of int tokens.
|
|
|
|
Examples:
|
|
>>> parser = CharParser(['a', 'b', 'c'])
|
|
>>> parser('abc')
|
|
[0, 1, 2]
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
labels: List[str],
|
|
*,
|
|
unk_id: int = -1,
|
|
blank_id: int = -1,
|
|
do_normalize: bool = True,
|
|
do_lowercase: bool = True,
|
|
):
|
|
"""Creates simple mapping char parser.
|
|
|
|
Args:
|
|
labels: List of labels to allocate indexes for. Essentially,
|
|
this is a id to str mapping.
|
|
unk_id: Index to choose for OOV words (default: -1).
|
|
blank_id: Index to filter out from final list of tokens
|
|
(default: -1).
|
|
do_normalize: True if apply normalization step before tokenizing
|
|
(default: True).
|
|
do_lowercase: True if apply lowercasing at normalizing step
|
|
(default: True).
|
|
"""
|
|
|
|
self._labels = labels
|
|
self._unk_id = unk_id
|
|
self._blank_id = blank_id
|
|
self._do_normalize = do_normalize
|
|
self._do_lowercase = do_lowercase
|
|
|
|
self._labels_map = {label: index for index, label in enumerate(labels)}
|
|
self._special_labels = set([label for label in labels if len(label) > 1])
|
|
|
|
def __call__(self, text: str) -> Optional[List[int]]:
|
|
if self._do_normalize:
|
|
text = self._normalize(text)
|
|
if text is None:
|
|
return None
|
|
|
|
text_tokens = self._tokenize(text)
|
|
|
|
return text_tokens
|
|
|
|
def _normalize(self, text: str) -> Optional[str]:
|
|
text = text.strip()
|
|
|
|
if self._do_lowercase:
|
|
text = text.lower()
|
|
|
|
return text
|
|
|
|
def _tokenize(self, text: str) -> List[int]:
|
|
tokens = []
|
|
# Split by word for find special labels.
|
|
for word_id, word in enumerate(text.split(' ')):
|
|
if word_id != 0: # Not first word - so we insert space before.
|
|
tokens.append(self._labels_map.get(' ', self._unk_id))
|
|
|
|
if word in self._special_labels:
|
|
tokens.append(self._labels_map[word])
|
|
continue
|
|
|
|
for char in word:
|
|
tokens.append(self._labels_map.get(char, self._unk_id))
|
|
|
|
# If unk_id == blank_id, OOV tokens are removed.
|
|
tokens = [token for token in tokens if token != self._blank_id]
|
|
|
|
return tokens
|
|
|
|
|
|
class ENCharParser(CharParser):
|
|
"""Incorporates english-specific parsing logic."""
|
|
|
|
PUNCTUATION_TO_REPLACE = frozendict.frozendict({'+': 'plus', '&': 'and', '%': 'percent'})
|
|
|
|
def __init__(self, abbreviation_version=None, make_table=True, *args, **kwargs):
|
|
"""Creates english-specific mapping char parser.
|
|
|
|
This class overrides normalizing implementation.
|
|
|
|
Args:
|
|
*args: Positional args to pass to `CharParser` constructor.
|
|
**kwargs: Key-value args to pass to `CharParser` constructor.
|
|
"""
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self._table = None
|
|
if make_table:
|
|
self._table = self.__make_trans_table()
|
|
self.abbreviation_version = abbreviation_version
|
|
|
|
def __make_trans_table(self):
|
|
punctuation = string.punctuation
|
|
|
|
for char in self.PUNCTUATION_TO_REPLACE:
|
|
punctuation = punctuation.replace(char, '')
|
|
|
|
for label in self._labels:
|
|
punctuation = punctuation.replace(label, '')
|
|
|
|
table = str.maketrans(punctuation, ' ' * len(punctuation))
|
|
|
|
return table
|
|
|
|
def _normalize(self, text: str) -> Optional[str]:
|
|
# noinspection PyBroadException
|
|
try:
|
|
text = cleaners.clean_text(
|
|
string=text,
|
|
table=self._table,
|
|
punctuation_to_replace=self.PUNCTUATION_TO_REPLACE,
|
|
abbreviation_version=self.abbreviation_version,
|
|
)
|
|
except Exception:
|
|
return None
|
|
|
|
return text
|
|
|
|
|
|
NAME_TO_PARSER = frozendict.frozendict({'base': CharParser, 'en': ENCharParser})
|
|
|
|
|
|
def make_parser(labels: Optional[List[str]] = None, name: str = 'base', **kwargs,) -> CharParser:
|
|
"""Creates parser from labels, set of arguments and concise parser name.
|
|
|
|
Args:
|
|
labels: List of labels to allocate indexes for. If set to
|
|
None then labels would be ascii table list. Essentially, this is a
|
|
id to str mapping (default: None).
|
|
name: Concise name of parser to create (default: 'base').
|
|
(default: -1).
|
|
**kwargs: Other set of kwargs to pass to parser constructor.
|
|
|
|
Returns:
|
|
Instance of `CharParser`.
|
|
|
|
Raises:
|
|
ValueError: For invalid parser name.
|
|
|
|
Examples:
|
|
>>> type(make_parser(['a', 'b', 'c'], 'en'))
|
|
ENCharParser
|
|
"""
|
|
|
|
if name not in NAME_TO_PARSER:
|
|
raise ValueError('Invalid parser name.')
|
|
|
|
if labels is None:
|
|
labels = list(string.printable)
|
|
|
|
parser_type = NAME_TO_PARSER[name]
|
|
parser = parser_type(labels=labels, **kwargs)
|
|
|
|
return parser
|