Files
modelscope/modelscope/preprocessors/speaker.py
shuli.cly 526e1371f5 Merge the speaker-turn-detection codes, local test finished
# Speaker Diarization Speaker-Turn Detection CR

和Dialogue-Detection一样,本模型是Speaker Diarization(`audio/speaker diarization`,语音/说话人日志)任务下的一个子模块。

本次提交的是基于文本进行判断的模型,本地模型的初始模型基于huggingface训练的,此提交中复用了部分 `nlp/token-classification` 模型的代码。为了方便后续维护以及与nlp方面代码解耦,在model、pipeline以及preprocessor中 **单独** 创建了相应模块并重新register。
Link: https://code.alibaba-inc.com/Ali-MaaS/MaaS-lib/codereview/13364720
* std first commit

* local test pass for speaker-turn-detection

* update speaker-turn-detection pipeline task outputs format; update pipeline outputs; update test scripts
2023-07-25 18:57:47 +08:00

315 lines
13 KiB
Python

# Copyright (c) Alibaba, Inc. and its affiliates.
from typing import Any, Dict, List, Tuple, Union
import torch
from modelscope.metainfo import Preprocessors
from modelscope.preprocessors import Preprocessor
from modelscope.preprocessors.builder import PREPROCESSORS
from modelscope.preprocessors.nlp.text_classification_preprocessor import \
TextClassificationPreprocessorBase
from modelscope.preprocessors.nlp.token_classification_preprocessor import (
NLPTokenizerForLSTM, TokenClassificationPreprocessorBase)
from modelscope.preprocessors.nlp.transformers_tokenizer import NLPTokenizer
from modelscope.utils.constant import Fields, ModeKeys
from modelscope.utils.hub import get_model_type, parse_label_mapping
from modelscope.utils.logger import get_logger
logger = get_logger()
@PREPROCESSORS.register_module(
Fields.audio, module_name=Preprocessors.sen_cls_tokenizer)
class SpeakerDiarizationDialogueDetectionPreprocessor(
TextClassificationPreprocessorBase):
def _tokenize_text(self, sequence1, sequence2=None, **kwargs):
if 'return_tensors' not in kwargs:
kwargs[
'return_tensors'] = 'pt' if self.mode == ModeKeys.INFERENCE else None
return self.nlp_tokenizer(sequence1, sequence2, **kwargs)
def __init__(self,
model_dir=None,
first_sequence: str = None,
second_sequence: str = None,
label: Union[str, List] = 'label',
label2id: Dict = None,
mode: str = ModeKeys.INFERENCE,
max_length: int = None,
use_fast: bool = None,
keep_original_columns=None,
**kwargs):
kwargs['truncation'] = kwargs.get('truncation', True)
kwargs['padding'] = kwargs.get('padding', 'max_length')
kwargs[
'max_length'] = max_length if max_length is not None else kwargs.get(
'sequence_length', 128)
kwargs.pop('sequence_length', None)
model_type = None
if model_dir is not None:
model_type = get_model_type(model_dir)
self.nlp_tokenizer = NLPTokenizer(
model_dir, model_type, use_fast=use_fast, tokenize_kwargs=kwargs)
super().__init__(model_dir, first_sequence, second_sequence, label,
label2id, mode, keep_original_columns)
@PREPROCESSORS.register_module(
Fields.audio, module_name=Preprocessors.token_cls_tokenizer)
class SpeakerDiarizationSemanticSpeakerTurnDetectionPreprocessor(
TokenClassificationPreprocessorBase):
def __init__(self,
model_dir: str = None,
first_sequence: str = 'text',
label: str = 'label',
label2id: Dict = None,
label_all_tokens: bool = False,
mode: str = ModeKeys.INFERENCE,
max_length=None,
use_fast=None,
keep_original_columns=None,
return_text=True,
**kwargs):
super().__init__(model_dir, first_sequence, label, label2id,
label_all_tokens, mode, keep_original_columns,
return_text)
model_type = None
if model_dir is not None:
model_type = get_model_type(model_dir)
kwargs['truncation'] = kwargs.get('truncation', True)
kwargs['padding'] = kwargs.get('padding', 'max_length')
kwargs[
'max_length'] = max_length if max_length is not None else kwargs.get(
'sequence_length', 128)
kwargs.pop('sequence_length', None)
kwargs['add_special_tokens'] = model_type != 'lstm'
self.nlp_tokenizer = NLPTokenizerForLSTM(
model_dir=model_dir,
model_type=model_type,
use_fast=use_fast,
tokenize_kwargs=kwargs)
def _tokenize_text(self, text: Union[str, List[str]], **kwargs):
tokens = text
if self.mode != ModeKeys.INFERENCE:
assert isinstance(tokens, list), 'Input needs to be lists in training and evaluating,' \
'because the length of the words and the labels need to be equal.'
is_split_into_words = self.nlp_tokenizer.get_tokenizer_kwarg(
'is_split_into_words', False)
if is_split_into_words:
# for supporting prompt seperator, should split twice. [SEP] for default.
sep_idx = tokens.find('[SEP]')
if sep_idx == -1 or self.is_lstm_model:
tokens = list(tokens)
else:
tmp_tokens = []
tmp_tokens.extend(list(tokens[:sep_idx]))
tmp_tokens.append('[SEP]')
tmp_tokens.extend(list(tokens[sep_idx + 5:]))
tokens = tmp_tokens
if is_split_into_words and self.mode == ModeKeys.INFERENCE:
encodings, word_ids = self._tokenize_text_by_words(
tokens, **kwargs)
elif self.nlp_tokenizer.tokenizer.is_fast:
encodings, word_ids = self._tokenize_text_with_fast_tokenizer(
tokens, **kwargs)
else:
encodings, word_ids = self._tokenize_text_with_slow_tokenizer(
tokens, **kwargs)
sep_idx = -1
for idx, token_id in enumerate(encodings['input_ids']):
if token_id == self.nlp_tokenizer.tokenizer.sep_token_id:
sep_idx = idx
break
if sep_idx != -1:
for i in range(sep_idx, len(encodings['label_mask'])):
encodings['label_mask'][i] = False
if self.mode == ModeKeys.INFERENCE:
for key in encodings.keys():
encodings[key] = torch.tensor(encodings[key]).unsqueeze(0)
else:
encodings.pop('offset_mapping', None)
return encodings, word_ids
def _tokenize_text_by_words(self, tokens, **kwargs):
input_ids = []
label_mask = []
offset_mapping = []
attention_mask = []
for offset, token in enumerate(tokens):
subtoken_ids = self.nlp_tokenizer.tokenizer.encode(
token, add_special_tokens=False)
if len(subtoken_ids) == 0:
subtoken_ids = [self.nlp_tokenizer.tokenizer.unk_token_id]
input_ids.extend(subtoken_ids)
attention_mask.extend([1] * len(subtoken_ids))
label_mask.extend([True] + [False] * (len(subtoken_ids) - 1))
offset_mapping.extend([(offset, offset + 1)])
padding = kwargs.get('padding',
self.nlp_tokenizer.get_tokenizer_kwarg('padding'))
max_length = kwargs.get(
'max_length',
kwargs.get('sequence_length',
self.nlp_tokenizer.get_tokenizer_kwarg('max_length')))
special_token = 1 if self.nlp_tokenizer.get_tokenizer_kwarg(
'add_special_tokens') else 0
if len(label_mask) > max_length - 2 * special_token:
label_mask = label_mask[:(max_length - 2 * special_token)]
input_ids = input_ids[:(max_length - 2 * special_token)]
offset_mapping = offset_mapping[:sum(label_mask)]
if padding == 'max_length':
label_mask = [False] * special_token + label_mask + \
[False] * (max_length - len(label_mask) - special_token)
offset_mapping = offset_mapping + [(0, 0)] * (
max_length - len(offset_mapping))
input_ids = [self.nlp_tokenizer.tokenizer.cls_token_id] * special_token + input_ids + \
[self.nlp_tokenizer.tokenizer.sep_token_id] * special_token + \
[self.nlp_tokenizer.tokenizer.pad_token_id] * (max_length - len(input_ids) - 2 * special_token)
attention_mask = attention_mask + [1] * (
special_token * 2) + [0] * (
max_length - len(attention_mask) - 2 * special_token)
else:
label_mask = [False] * special_token + label_mask + \
[False] * special_token
input_ids = [self.nlp_tokenizer.tokenizer.cls_token_id] * special_token + input_ids + \
[self.nlp_tokenizer.tokenizer.sep_token_id] * special_token
attention_mask = attention_mask + [1] * (special_token * 2)
encodings = {
'input_ids': input_ids,
'attention_mask': attention_mask,
'label_mask': label_mask,
'offset_mapping': offset_mapping,
}
return encodings, None
def _tokenize_text_with_fast_tokenizer(self, tokens, **kwargs):
is_split_into_words = isinstance(tokens, list)
encodings = self.nlp_tokenizer(
tokens,
return_offsets_mapping=True,
is_split_into_words=is_split_into_words,
**kwargs)
label_mask = []
word_ids = encodings.word_ids()
offset_mapping = []
for i in range(len(word_ids)):
if word_ids[i] is None:
label_mask.append(False)
elif word_ids[i] == word_ids[i - 1]:
label_mask.append(False)
if not is_split_into_words:
offset_mapping[-1] = (offset_mapping[-1][0],
encodings['offset_mapping'][i][1])
else:
label_mask.append(True)
if is_split_into_words:
offset_mapping.append((word_ids[i], word_ids[i] + 1))
else:
offset_mapping.append(encodings['offset_mapping'][i])
padding = self.nlp_tokenizer.get_tokenizer_kwarg('padding')
if padding == 'max_length':
offset_mapping = offset_mapping + [(0, 0)] * (
len(label_mask) - len(offset_mapping))
encodings['offset_mapping'] = offset_mapping
encodings['label_mask'] = label_mask
return encodings, word_ids
def _tokenize_text_with_slow_tokenizer(self, tokens, **kwargs):
assert self.mode == ModeKeys.INFERENCE and isinstance(tokens, str), \
'Slow tokenizer now only support str input in inference mode. If you are training models, ' \
'please consider using the fast tokenizer.'
word_ids = None
encodings = self.nlp_tokenizer(
tokens, is_split_into_words=False, **kwargs)
tokenizer_name = self.nlp_tokenizer.get_tokenizer_class()
method = 'get_label_mask_and_offset_mapping_' + tokenizer_name
if not hasattr(self, method):
raise RuntimeError(
f'No `{method}` method defined for '
f'tokenizer {tokenizer_name}, please use a fast tokenizer instead, or '
f'try to implement a `{method}` method')
label_mask, offset_mapping = getattr(self, method)(tokens)
padding = kwargs.get('padding',
self.nlp_tokenizer.get_tokenizer_kwarg('padding'))
max_length = kwargs.get(
'max_length', self.nlp_tokenizer.get_tokenizer_kwarg('max_length'))
special_token = 1 if kwargs.get(
'add_special_tokens',
self.nlp_tokenizer.get_tokenizer_kwarg(
'add_special_tokens')) else 0
if len(label_mask) > max_length - 2 * special_token:
label_mask = label_mask[:(max_length - 2 * special_token)]
offset_mapping = offset_mapping[:sum(label_mask)]
if padding == 'max_length':
label_mask = [False] * special_token + label_mask + \
[False] * (max_length - len(label_mask) - special_token)
offset_mapping = offset_mapping + [(0, 0)] * (
max_length - len(offset_mapping))
else:
label_mask = [False] * special_token + label_mask + \
[False] * special_token
encodings['offset_mapping'] = offset_mapping
encodings['label_mask'] = label_mask
return encodings, word_ids
def get_label_mask_and_offset_mapping_BertTokenizer(self, text):
label_mask = []
offset_mapping = []
tokens = self.nlp_tokenizer.tokenizer.tokenize(text)
offset = 0
for token in tokens:
is_start = (token[:2] != '##')
if is_start:
label_mask.append(True)
else:
token = token[2:]
label_mask.append(False)
start = offset + text[offset:].index(token)
end = start + len(token)
if is_start:
offset_mapping.append((start, end))
else:
offset_mapping[-1] = (offset_mapping[-1][0], end)
offset = end
return label_mask, offset_mapping
def get_label_mask_and_offset_mapping_XLMRobertaTokenizer(self, text):
label_mask = []
offset_mapping = []
tokens = self.nlp_tokenizer.tokenizer.tokenize(text)
offset = 0
last_is_blank = False
for token in tokens:
is_start = (token[0] == '_')
if is_start:
token = token[1:]
label_mask.append(True)
if len(token) == 0:
last_is_blank = True
continue
else:
label_mask.append(False)
start = offset + text[offset:].index(token)
end = start + len(token)
if last_is_blank or is_start:
offset_mapping.append((start, end))
else:
offset_mapping[-1] = (offset_mapping[-1][0], end)
offset = end
last_is_blank = False
return label_mask, offset_mapping