first commit for takway.ai
This commit is contained in:
commit
d7a6de771c
3256 files changed
+795173
No files matched your search
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,260 @@
|
||||
import torch
|
||||
import random
|
||||
|
||||
from funasr.register import tables
|
||||
from funasr.utils.load_utils import extract_fbank, load_audio_text_image_video
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioDataset")
|
||||
class AudioDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
AudioDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf")
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf"))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.int_pad_value = int_pad_value
|
||||
self.float_pad_value = float_pad_value
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
if self.tokenizer:
|
||||
ids = self.tokenizer.encode(target)
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
else:
|
||||
ids = target
|
||||
text = ids
|
||||
ids_lengths = len(ids)
|
||||
text_lengths = torch.tensor([ids_lengths], dtype=torch.int32)
|
||||
|
||||
return {
|
||||
"speech": speech[0, :, :],
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
return outputs
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioDatasetHotword")
|
||||
class AudioDatasetHotword(AudioDataset):
|
||||
# for finetuning contextual_paraformer and seaco_paraformer
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
seaco_id: bool = 0,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.seaco_id = seaco_id
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
if self.tokenizer:
|
||||
ids = self.tokenizer.encode(target)
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
else:
|
||||
ids = target
|
||||
text = ids
|
||||
ids_lengths = len(ids)
|
||||
text_lengths = torch.tensor([ids_lengths], dtype=torch.int32)
|
||||
|
||||
def generate_index(
|
||||
length,
|
||||
hotword_min_length=2,
|
||||
hotword_max_length=8,
|
||||
sample_rate=0.75,
|
||||
double_rate=0.1,
|
||||
pre_prob=0.0,
|
||||
pre_index=None,
|
||||
pre_hwlist=None,
|
||||
):
|
||||
if length < hotword_min_length:
|
||||
return [-1]
|
||||
if random.random() < sample_rate:
|
||||
if pre_prob > 0 and random.random() < pre_prob and pre_index is not None:
|
||||
return pre_index
|
||||
if length == hotword_min_length:
|
||||
return [0, length - 1]
|
||||
elif (
|
||||
random.random() < double_rate
|
||||
and length > hotword_max_length + hotword_min_length + 2
|
||||
):
|
||||
# sample two hotwords in a sentence
|
||||
_max_hw_length = min(hotword_max_length, length // 2)
|
||||
# first hotword
|
||||
start1 = random.randint(0, length // 3)
|
||||
end1 = random.randint(
|
||||
start1 + hotword_min_length - 1, start1 + _max_hw_length - 1
|
||||
)
|
||||
# second hotword
|
||||
start2 = random.randint(end1 + 1, length - hotword_min_length)
|
||||
end2 = random.randint(
|
||||
min(length - 1, start2 + hotword_min_length - 1),
|
||||
min(length - 1, start2 + hotword_max_length - 1),
|
||||
)
|
||||
return [start1, end1, start2, end2]
|
||||
else: # single hotword
|
||||
start = random.randint(0, length - hotword_min_length)
|
||||
end = random.randint(
|
||||
min(length - 1, start + hotword_min_length - 1),
|
||||
min(length - 1, start + hotword_max_length - 1),
|
||||
)
|
||||
return [start, end]
|
||||
else:
|
||||
return [-1]
|
||||
|
||||
hotword_indx = generate_index(text_lengths[0])
|
||||
return {
|
||||
"speech": speech[0, :, :],
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"hotword_indx": hotword_indx,
|
||||
"seaco_id": self.seaco_id,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
hotword_indxs = []
|
||||
seaco_id = samples[0]["seaco_id"]
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key == "seaco_id":
|
||||
continue
|
||||
elif key == "hotword_indx":
|
||||
hotword_indxs.append(sample[key])
|
||||
else:
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
|
||||
hotword_list, hotword_lengths = [], []
|
||||
text = outputs["text"]
|
||||
seaco_label_pad = torch.ones_like(text) * -1 if seaco_id else None
|
||||
for b, (hotword_indx, one_text, length) in enumerate(
|
||||
zip(hotword_indxs, text, outputs["text_lengths"])
|
||||
):
|
||||
length = length[0]
|
||||
if seaco_label_pad is not None:
|
||||
seaco_label_pad[b][:length] = seaco_id
|
||||
if hotword_indx[0] != -1:
|
||||
start, end = int(hotword_indx[0]), int(hotword_indx[1])
|
||||
hotword = one_text[start : end + 1]
|
||||
hotword_list.append(hotword)
|
||||
hotword_lengths.append(end - start + 1)
|
||||
if seaco_label_pad is not None:
|
||||
seaco_label_pad[b][start : end + 1] = one_text[start : end + 1]
|
||||
if len(hotword_indx) == 4 and hotword_indx[2] != -1:
|
||||
# the second hotword if exist
|
||||
start, end = int(hotword_indx[2]), int(hotword_indx[3])
|
||||
hotword_list.append(one_text[start : end + 1])
|
||||
hotword_lengths.append(end - start + 1)
|
||||
if seaco_label_pad is not None:
|
||||
seaco_label_pad[b][start : end + 1] = one_text[start : end + 1]
|
||||
hotword_list.append(torch.tensor([1]))
|
||||
hotword_lengths.append(1)
|
||||
hotword_pad = torch.nn.utils.rnn.pad_sequence(
|
||||
hotword_list, batch_first=True, padding_value=0
|
||||
)
|
||||
outputs["hotword_pad"] = hotword_pad
|
||||
outputs["hotword_lengths"] = torch.tensor(hotword_lengths, dtype=torch.int32)
|
||||
if seaco_label_pad is not None:
|
||||
outputs["seaco_label_pad"] = seaco_label_pad
|
||||
return outputs
|
||||
@@ -0,0 +1,161 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
import logging
|
||||
import math
|
||||
import torch.distributed as dist
|
||||
from torch.utils.data import DistributedSampler
|
||||
from torch.utils.data import BatchSampler, Sampler
|
||||
import torch.distributed as dist
|
||||
import random
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
@tables.register("batch_sampler_classes", "EspnetStyleBatchSampler")
|
||||
def EspnetStyleBatchSampler_fn(dataset, **kwargs):
|
||||
dataloader_args = {}
|
||||
|
||||
batch_sampler = EspnetStyleBatchSampler(dataset, **kwargs)
|
||||
dataloader_args["batch_sampler"] = batch_sampler
|
||||
dataloader_args["num_workers"] = kwargs.get("num_workers", 4)
|
||||
dataloader_args["pin_memory"] = kwargs.get("pin_memory", True)
|
||||
|
||||
return dataloader_args
|
||||
|
||||
|
||||
import torch
|
||||
from torch.utils.data import Dataset, DistributedSampler
|
||||
import math
|
||||
import random
|
||||
|
||||
|
||||
class EspnetStyleBatchSampler(DistributedSampler):
|
||||
def __init__(
|
||||
self,
|
||||
dataset,
|
||||
batch_size,
|
||||
batch_type="token",
|
||||
rank=None,
|
||||
num_replicas=None,
|
||||
rank_split=False,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
is_training: bool = True,
|
||||
sort_size: int = 1024,
|
||||
start_step: int = 0,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
num_replicas = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
num_replicas = 1
|
||||
# if rank_split:
|
||||
# logging.info(f"Warning, rank_split: {rank_split}, batch and shuffle data in local rank")
|
||||
# rank = 0
|
||||
# num_replicas = 1
|
||||
self.rank = rank
|
||||
self.num_replicas = num_replicas
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
self.batch_type = batch_type
|
||||
self.is_training = is_training
|
||||
self.shuffle = shuffle and is_training
|
||||
self.drop_last = drop_last
|
||||
|
||||
self.total_size = len(self.dataset)
|
||||
self.num_samples = int(math.ceil(self.total_size / self.num_replicas))
|
||||
self.epoch = 0
|
||||
self.sort_size = sort_size * num_replicas
|
||||
self.max_token_length = kwargs.get("max_token_length", 2048)
|
||||
self.min_token_length = kwargs.get("min_token_length", 0)
|
||||
self.length_scale_source = kwargs.get("length_scale_source", 1.0)
|
||||
self.start_step = start_step
|
||||
if self.start_step > 0:
|
||||
logging.info(f"Warning, start_step > 0, dataloader start from step: {self.start_step}")
|
||||
# super().__init__(dataset, num_replicas=num_replicas, rank=rank,
|
||||
# shuffle=shuffle, drop_last=drop_last)
|
||||
|
||||
def __iter__(self):
|
||||
if self.shuffle:
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
random.seed(self.epoch)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset)))
|
||||
|
||||
# Sort indices by sample length
|
||||
sorted_indices = sorted(indices, key=lambda idx: self.dataset.get_source_len(idx))
|
||||
|
||||
# Organize batches based on 'length' or 'example'
|
||||
buffer_batches = []
|
||||
batch = []
|
||||
max_len_in_batch = 0 # Tracks the max sample length within the current batch
|
||||
|
||||
for idx in sorted_indices:
|
||||
|
||||
# original_sample_length = self.dataset.get_source_len(idx)
|
||||
# if (
|
||||
# original_sample_length < self.min_token_length
|
||||
# or original_sample_length > self.max_token_length
|
||||
# ): # Skip samples that exceed the max length
|
||||
# continue
|
||||
|
||||
# sample_length = 1 if self.batch_type == "example" else original_sample_length
|
||||
|
||||
# Set sample_length based on the batch type
|
||||
if self.batch_type == "example":
|
||||
sample_length = 1
|
||||
elif self.batch_type == "token":
|
||||
sample_length = self.dataset.get_source_len(idx) + int(
|
||||
self.dataset.get_target_len(idx) * 1.2
|
||||
)
|
||||
else:
|
||||
sample_length = self.dataset.get_source_len(idx)
|
||||
# Calculate potential batch size with the new sample
|
||||
potential_batch_length = max(max_len_in_batch, sample_length) * (len(batch) + 1)
|
||||
# Add index to batch if it doesn't exceed batch size limit
|
||||
if potential_batch_length <= self.batch_size:
|
||||
batch.append(idx)
|
||||
max_len_in_batch = max(max_len_in_batch, sample_length)
|
||||
else:
|
||||
# Save the current batch and start a new one
|
||||
buffer_batches.append(batch)
|
||||
batch = [idx]
|
||||
max_len_in_batch = sample_length
|
||||
|
||||
# Add the last batch if it shouldn't be dropped
|
||||
if batch and (not self.drop_last or len(batch) * max_len_in_batch == self.batch_size):
|
||||
buffer_batches.append(batch)
|
||||
|
||||
# Shuffle the list of batches
|
||||
if self.shuffle:
|
||||
random.seed(self.epoch)
|
||||
random.shuffle(buffer_batches)
|
||||
|
||||
# Ensure each rank gets the same number of batches
|
||||
batches_per_rank = int(math.ceil(len(buffer_batches) / self.num_replicas))
|
||||
total_batches_needed = batches_per_rank * self.num_replicas
|
||||
extra_batches = total_batches_needed - len(buffer_batches)
|
||||
# Add extra batches by random selection, if needed
|
||||
buffer_batches += random.choices(buffer_batches, k=extra_batches)
|
||||
|
||||
# Allocate the batches to the current rank
|
||||
start_idx = self.rank * batches_per_rank
|
||||
end_idx = start_idx + batches_per_rank
|
||||
rank_batches = buffer_batches[start_idx + self.start_step : end_idx]
|
||||
logging.info(
|
||||
f"rank: {self.rank}, dataloader start from step: {self.start_step}, batch_num: {end_idx-start_idx}, batch_num_after_step: {len(rank_batches)}"
|
||||
)
|
||||
# Return an iterator over the batches for the current rank
|
||||
return iter(rank_batches)
|
||||
|
||||
def __len__(self):
|
||||
# Calculate the number of batches per epoch for the current rank
|
||||
return 1
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
# Set the epoch for shuffling
|
||||
self.epoch = epoch
|
||||
@@ -0,0 +1,144 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
|
||||
import librosa
|
||||
import random
|
||||
import torch.distributed as dist
|
||||
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
@tables.register("index_ds_classes", "IndexDSJsonl")
|
||||
@tables.register("index_ds_classes", "IndexDSJsonlRankFull")
|
||||
@tables.register("index_ds_classes", "IndexDSJsonlRankSplit")
|
||||
class IndexDSJsonlRankFull(torch.utils.data.Dataset):
|
||||
|
||||
def __init__(self, path: str, **kwargs):
|
||||
super().__init__()
|
||||
self.max_source_length = kwargs.get("max_source_length", 2048)
|
||||
self.min_source_length = kwargs.get("min_source_length", 0)
|
||||
self.max_target_length = kwargs.get("max_target_length", 2048)
|
||||
self.min_target_length = kwargs.get("min_target_length", 0)
|
||||
self.max_token_length = kwargs.get("max_token_length", 2200)
|
||||
|
||||
is_training = kwargs.get("is_training", True)
|
||||
if not (path.endswith(".jsonl") or path.endswith(".json")):
|
||||
# jsonl list file
|
||||
data_split_num = kwargs.get("data_split_num", 1)
|
||||
data_split_i = kwargs.get("data_split_i", 0)
|
||||
|
||||
if not is_training:
|
||||
data_split_num = 1
|
||||
data_split_i = 0
|
||||
with open(path, encoding="utf-8") as fin:
|
||||
file_list_all = fin.readlines()
|
||||
|
||||
num_per_slice = (len(file_list_all) - 1) // data_split_num + 1 # 16
|
||||
file_list = file_list_all[
|
||||
data_split_i * num_per_slice : (data_split_i + 1) * num_per_slice
|
||||
]
|
||||
logging.info(
|
||||
f"is_training: {is_training}, data_split_num: {data_split_num}, data_split_i: {data_split_i}, \nfile_list: {file_list}, \nfile_list_all: {file_list_all}"
|
||||
)
|
||||
|
||||
else:
|
||||
file_list = [path]
|
||||
|
||||
# total_num = len(file_list)
|
||||
# try:
|
||||
# rank = dist.get_rank()
|
||||
# world_size = dist.get_world_size()
|
||||
# except:
|
||||
# rank = 0
|
||||
# world_size = 1
|
||||
# logging.info("distributed is not initialized, only single shard")
|
||||
#
|
||||
# if not kwargs.get("rank_split", False):
|
||||
# logging.info(f"Warning, rank_split disenabled, batch and shuffle data in global")
|
||||
# rank = 0
|
||||
# world_size = 1
|
||||
#
|
||||
# num_per_rank = total_num // world_size
|
||||
# if num_per_rank * world_size < total_num:
|
||||
# logging.info(f"Warning, jsonl file:{total_num} could not be divided by world_size: {world_size}, {path}")
|
||||
# total_num_needed = num_per_rank * world_size
|
||||
#
|
||||
# extra_num = total_num_needed - total_num
|
||||
# file_list_tmp = random.choices(file_list, k=extra_num)
|
||||
# file_list += file_list_tmp
|
||||
# logging.info(f"Warning, after random choices: {file_list}")
|
||||
#
|
||||
# file_list_rank = file_list[rank * num_per_rank:(rank + 1) * num_per_rank]
|
||||
#
|
||||
# logging.info(
|
||||
# f"is_training: {is_training}, file_list_rank: {file_list_rank}")
|
||||
|
||||
# contents = []
|
||||
# for file_json in file_list_rank:
|
||||
contents = []
|
||||
for file_json in file_list:
|
||||
with open(file_json.strip(), encoding="utf-8") as fin:
|
||||
for line in fin:
|
||||
data = json.loads(line.strip())
|
||||
if "text" in data: # for sft
|
||||
contents.append(data["text"])
|
||||
if "source" in data: # for speech lab pretrain
|
||||
prompt = data.get("prompt", "<ASR>")
|
||||
source = data["source"].replace(
|
||||
"/cpfs01", "/cpfs_speech/data"
|
||||
) # only use in alibaba gpu group: .replace("/cpfs01", "/cpfs_speech/data")
|
||||
target = data["target"]
|
||||
source_len = data.get("source_len", 1)
|
||||
target_len = data.get("target_len", 0)
|
||||
if "aishell" in source:
|
||||
target = target.replace(" ", "")
|
||||
if (
|
||||
source_len < self.min_source_length
|
||||
or source_len > self.max_source_length
|
||||
):
|
||||
continue
|
||||
if (
|
||||
target_len < self.min_target_length
|
||||
or target_len > self.max_target_length
|
||||
):
|
||||
continue
|
||||
|
||||
if (source_len + target_len) > self.max_token_length:
|
||||
continue
|
||||
|
||||
contents_i = {
|
||||
"source": source,
|
||||
"prompt": prompt,
|
||||
"target": target,
|
||||
"source_len": source_len,
|
||||
"target_len": target_len,
|
||||
}
|
||||
text_language = data.get("text_language", None)
|
||||
if text_language is not None:
|
||||
contents_i["text_language"] = text_language
|
||||
# audio_language = data.get("audio_language", None)
|
||||
# if audio_language is not None:
|
||||
# contents_i["audio_language"] = audio_language
|
||||
contents.append(contents_i)
|
||||
|
||||
self.contents = contents
|
||||
|
||||
logging.info("total_num of samplers: {}, {}".format(len(self.contents), path))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.contents)
|
||||
|
||||
def __getitem__(self, index):
|
||||
|
||||
data = self.contents[index]
|
||||
|
||||
return data
|
||||
|
||||
def get_source_len(self, data_dict):
|
||||
return data_dict.get("source_len", 1)
|
||||
|
||||
def get_target_len(self, data_dict):
|
||||
|
||||
return data_dict.get("target_len", 0)
|
||||
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
import hydra
|
||||
from omegaconf import DictConfig, OmegaConf
|
||||
import concurrent.futures
|
||||
import librosa
|
||||
import torch.distributed as dist
|
||||
|
||||
|
||||
def gen_scp_from_jsonl(jsonl_file, data_type_list, wav_scp_file, text_file):
|
||||
|
||||
wav_f = open(wav_scp_file, "w")
|
||||
text_f = open(text_file, "w")
|
||||
with open(jsonl_file, encoding="utf-8") as fin:
|
||||
for line in fin:
|
||||
data = json.loads(line.strip())
|
||||
|
||||
prompt = data.get("prompt", "<ASR>")
|
||||
source = data[data_type_list[0]]
|
||||
target = data[data_type_list[1]]
|
||||
source_len = data.get("source_len", 1)
|
||||
target_len = data.get("target_len", 0)
|
||||
if "aishell" in source:
|
||||
target = target.replace(" ", "")
|
||||
key = data["key"]
|
||||
wav_f.write(f"{key}\t{source}\n")
|
||||
wav_f.flush()
|
||||
text_f.write(f"{key}\t{target}\n")
|
||||
text_f.flush()
|
||||
|
||||
wav_f.close()
|
||||
text_f.close()
|
||||
|
||||
|
||||
@hydra.main(config_name=None, version_base=None)
|
||||
def main_hydra(cfg: DictConfig):
|
||||
|
||||
kwargs = OmegaConf.to_container(cfg, resolve=True)
|
||||
print(kwargs)
|
||||
|
||||
scp_file_list = kwargs.get(
|
||||
"scp_file_list",
|
||||
("/Users/zhifu/funasr1.0/test_local/wav.scp", "/Users/zhifu/funasr1.0/test_local/text.txt"),
|
||||
)
|
||||
if isinstance(scp_file_list, str):
|
||||
scp_file_list = eval(scp_file_list)
|
||||
data_type_list = kwargs.get("data_type_list", ("source", "target"))
|
||||
jsonl_file = kwargs.get(
|
||||
"jsonl_file_in", "/Users/zhifu/funasr1.0/test_local/audio_datasets.jsonl"
|
||||
)
|
||||
gen_scp_from_jsonl(jsonl_file, data_type_list, *scp_file_list)
|
||||
|
||||
|
||||
"""
|
||||
python -m funasr.datasets.audio_datasets.json2scp \
|
||||
++scp_file_list='["/Users/zhifu/funasr1.0/test_local/wav.scp", "/Users/zhifu/funasr1.0/test_local/text.txt"]' \
|
||||
++data_type_list='["source", "target"]' \
|
||||
++jsonl_file_in=/Users/zhifu/funasr1.0/test_local/audio_datasets.jsonl
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_hydra()
|
||||
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
import concurrent.futures
|
||||
import librosa
|
||||
import torch.distributed as dist
|
||||
from typing import Collection
|
||||
import torch
|
||||
import torchaudio
|
||||
from torch import nn
|
||||
import random
|
||||
import re
|
||||
from funasr.tokenizer.cleaner import TextCleaner
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
@tables.register("preprocessor_classes", "SpeechPreprocessSpeedPerturb")
|
||||
class SpeechPreprocessSpeedPerturb(nn.Module):
|
||||
def __init__(self, speed_perturb: list = None, **kwargs):
|
||||
super().__init__()
|
||||
self.speed_perturb = speed_perturb
|
||||
|
||||
def forward(self, waveform, fs, **kwargs):
|
||||
if self.speed_perturb is None:
|
||||
return waveform
|
||||
speed = random.choice(self.speed_perturb)
|
||||
if speed != 1.0:
|
||||
if not isinstance(waveform, torch.Tensor):
|
||||
waveform = torch.tensor(waveform)
|
||||
waveform, _ = torchaudio.sox_effects.apply_effects_tensor(
|
||||
waveform.view(1, -1), fs, [["speed", str(speed)], ["rate", str(fs)]]
|
||||
)
|
||||
waveform = waveform.view(-1)
|
||||
|
||||
return waveform
|
||||
|
||||
|
||||
@tables.register("preprocessor_classes", "TextPreprocessSegDict")
|
||||
class TextPreprocessSegDict(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
seg_dict: str = None,
|
||||
text_cleaner: Collection[str] = None,
|
||||
split_with_space: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.text_cleaner = TextCleaner(text_cleaner)
|
||||
|
||||
def forward(self, text, **kwargs):
|
||||
text = self.text_cleaner(text)
|
||||
|
||||
return text
|
||||
@@ -0,0 +1,467 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
import torch.distributed as dist
|
||||
from torch.utils.data import DistributedSampler
|
||||
from torch.utils.data import BatchSampler, Sampler
|
||||
import torch.distributed as dist
|
||||
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
@tables.register("batch_sampler_classes", "BatchSampler")
|
||||
@tables.register("batch_sampler_classes", "CustomDistributedBatchSampler")
|
||||
@tables.register("batch_sampler_classes", "CustomDistributedDynamicBatchSampler")
|
||||
@tables.register("batch_sampler_classes", "DynamicBatchLocalShuffleSampler")
|
||||
@tables.register("batch_sampler_classes", "RankFullLocalShuffleBatchSampler")
|
||||
@tables.register("batch_sampler_classes", "RankFullLocalShuffleDynamicBatchSampler")
|
||||
def CustomDistributedBatchSampler_fn(dataset, **kwargs):
|
||||
dataloader_args = {}
|
||||
batch_type = kwargs.get("batch_type", "example")
|
||||
if batch_type == "example":
|
||||
batch_sampler = CustomDistributedBatchSampler(dataset, **kwargs)
|
||||
|
||||
else:
|
||||
if kwargs.get("sort_size", -1) > 0:
|
||||
batch_sampler = CustomDistributedBufferDynamicBatchSampler(dataset, **kwargs)
|
||||
else:
|
||||
batch_sampler = CustomDistributedDynamicBatchSampler(dataset, **kwargs)
|
||||
# batch_sampler = CustomDistributedDynamicBatchSampler(dataset, **kwargs)
|
||||
|
||||
dataloader_args["batch_sampler"] = batch_sampler
|
||||
dataloader_args["num_workers"] = kwargs.get("num_workers", 4)
|
||||
dataloader_args["pin_memory"] = kwargs.get("pin_memory", True)
|
||||
|
||||
return dataloader_args
|
||||
|
||||
|
||||
class CustomDistributedBatchSampler(Sampler):
|
||||
def __init__(
|
||||
self,
|
||||
dataset,
|
||||
batch_size,
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
is_training: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
num_replicas = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
num_replicas = 1
|
||||
self.rank = rank
|
||||
self.num_replicas = num_replicas
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
self.is_training = is_training
|
||||
self.shuffle = shuffle and is_training
|
||||
self.drop_last = drop_last
|
||||
# self.total_size = len(dataset)
|
||||
if self.drop_last:
|
||||
self.total_size = (len(self.dataset) // (batch_size * num_replicas)) * (
|
||||
batch_size * num_replicas
|
||||
)
|
||||
else:
|
||||
self.total_size = math.ceil(len(self.dataset) / (batch_size * num_replicas)) * (
|
||||
batch_size * num_replicas
|
||||
)
|
||||
self.num_samples = int(self.total_size // self.num_replicas)
|
||||
self.epoch = 0
|
||||
self.max_token_length = kwargs.get("max_token_length", None)
|
||||
self.length_scale_source = kwargs.get("length_scale_source", 1.0)
|
||||
|
||||
def __iter__(self):
|
||||
# Generate a list of indices
|
||||
if self.shuffle:
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset)))
|
||||
|
||||
# Add extra samples to make it evenly divisible
|
||||
padding_size = self.total_size - len(indices)
|
||||
if padding_size <= len(indices):
|
||||
indices += indices[:padding_size]
|
||||
else:
|
||||
indices += (
|
||||
indices * (padding_size // len(indices)) + indices[: padding_size % len(indices)]
|
||||
)
|
||||
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
# Subsample
|
||||
indices = indices[self.rank : self.total_size : self.num_replicas]
|
||||
assert len(indices) == self.num_samples
|
||||
|
||||
# Filter out indices with length greater than the max length, if provided
|
||||
if self.max_token_length is not None:
|
||||
filtered_indices = []
|
||||
for idx in indices:
|
||||
source_len = self.dataset.get_source_len(idx) / self.length_scale_source
|
||||
if source_len <= self.max_token_length:
|
||||
filtered_indices.append(idx)
|
||||
indices = filtered_indices
|
||||
|
||||
# Now that we have only the indices for this replica, chunk them into batches
|
||||
batches = [
|
||||
indices[i : i + self.batch_size] for i in range(0, len(indices), self.batch_size)
|
||||
]
|
||||
|
||||
# Drop the last batch if it's not full and drop_last is True
|
||||
if self.drop_last and len(batches[-1]) != self.batch_size:
|
||||
batches = batches[:-1]
|
||||
|
||||
return iter(batches)
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return self.num_samples // self.batch_size
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
|
||||
|
||||
class CustomDistributedBufferBatchSampler(Sampler):
|
||||
def __init__(
|
||||
self,
|
||||
dataset,
|
||||
batch_size,
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
is_training: bool = True,
|
||||
sort_size: int = 1024,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
num_replicas = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
num_replicas = 1
|
||||
self.rank = rank
|
||||
self.num_replicas = num_replicas
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
self.is_training = is_training
|
||||
self.shuffle = shuffle and is_training
|
||||
self.drop_last = drop_last
|
||||
# self.total_size = len(dataset)
|
||||
if self.drop_last:
|
||||
self.total_size = (len(self.dataset) // (batch_size * num_replicas)) * (
|
||||
batch_size * num_replicas
|
||||
)
|
||||
else:
|
||||
self.total_size = math.ceil(len(self.dataset) / (batch_size * num_replicas)) * (
|
||||
batch_size * num_replicas
|
||||
)
|
||||
self.num_samples = int(self.total_size // self.num_replicas)
|
||||
self.epoch = 0
|
||||
self.max_token_length = kwargs.get("max_token_length", None)
|
||||
self.length_scale_source = kwargs.get("length_scale_source", 1.0)
|
||||
self.sort_size = sort_size
|
||||
|
||||
def __iter__(self):
|
||||
# Generate a list of indices
|
||||
if self.shuffle:
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset)))
|
||||
|
||||
# Add extra samples to make it evenly divisible
|
||||
padding_size = self.total_size - len(indices)
|
||||
if padding_size <= len(indices):
|
||||
indices += indices[:padding_size]
|
||||
else:
|
||||
indices += (
|
||||
indices * (padding_size // len(indices)) + indices[: padding_size % len(indices)]
|
||||
)
|
||||
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
# Subsample
|
||||
indices = indices[self.rank : self.total_size : self.num_replicas]
|
||||
assert len(indices) == self.num_samples
|
||||
|
||||
# Filter out indices with length greater than the max length, if provided
|
||||
if self.max_token_length is not None:
|
||||
filtered_indices = []
|
||||
for idx in indices:
|
||||
source_len = self.dataset.get_source_len(idx) / self.length_scale_source
|
||||
if source_len <= self.max_token_length:
|
||||
filtered_indices.append(idx)
|
||||
indices = filtered_indices
|
||||
|
||||
# Buffer sorting logic
|
||||
sorted_batches = []
|
||||
buffer = []
|
||||
|
||||
for idx in indices:
|
||||
buffer.append(idx)
|
||||
if len(buffer) >= self.sort_size:
|
||||
# Sort the buffer based on some criteria, e.g., dataset sample length
|
||||
buffer.sort(key=lambda x: self.dataset.get_source_len(x))
|
||||
sorted_batches.extend(self._create_batches_from_buffer(buffer))
|
||||
buffer = []
|
||||
|
||||
# Handle the remaining items in the buffer
|
||||
if buffer:
|
||||
buffer.sort(key=lambda x: self.dataset.get_source_len(x))
|
||||
sorted_batches.extend(self._create_batches_from_buffer(buffer))
|
||||
|
||||
return iter(sorted_batches)
|
||||
|
||||
def _create_batches_from_buffer(self, buffer):
|
||||
# Function to convert the sorted buffer into batches
|
||||
batched_buffer = [
|
||||
buffer[i : i + self.batch_size] for i in range(0, len(buffer), self.batch_size)
|
||||
]
|
||||
if self.drop_last and len(batched_buffer[-1]) != self.batch_size:
|
||||
batched_buffer = batched_buffer[:-1]
|
||||
return batched_buffer
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return self.num_samples // self.batch_size
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
|
||||
|
||||
class CustomDistributedDynamicBatchSampler(DistributedSampler):
|
||||
def __init__(
|
||||
self,
|
||||
dataset,
|
||||
batch_size,
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
is_training: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
num_replicas = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
num_replicas = 1
|
||||
self.rank = rank
|
||||
self.num_replicas = num_replicas
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
self.is_training = is_training
|
||||
self.shuffle = shuffle and is_training
|
||||
self.drop_last = drop_last
|
||||
|
||||
self.total_size = len(self.dataset)
|
||||
# self.num_samples = int(math.ceil(self.total_size / self.num_replicas))
|
||||
self.epoch = 0
|
||||
self.max_token_length = kwargs.get("max_token_length", 2048)
|
||||
self.length_scale_source = kwargs.get("length_scale_source", 1.0)
|
||||
|
||||
def __iter__(self):
|
||||
if self.shuffle:
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset)))
|
||||
|
||||
indices = indices[self.rank : self.total_size : self.num_replicas]
|
||||
|
||||
batches = []
|
||||
batch = []
|
||||
max_len_in_batch = 0
|
||||
current_batch_length = 0
|
||||
|
||||
for idx in indices:
|
||||
sample_length = self.dataset.get_source_len(idx)
|
||||
if sample_length > self.max_token_length:
|
||||
continue
|
||||
potential_batch_length = (
|
||||
max_len_in_batch if sample_length < max_len_in_batch else sample_length
|
||||
) * (len(batch) + 1)
|
||||
|
||||
if potential_batch_length <= self.batch_size:
|
||||
batch.append(idx)
|
||||
if sample_length > max_len_in_batch:
|
||||
max_len_in_batch = sample_length
|
||||
# current_batch_length = max_len_in_batch * len(batch)
|
||||
else:
|
||||
batches.append(batch)
|
||||
batch = [idx]
|
||||
max_len_in_batch = sample_length
|
||||
# current_batch_length = max_len_in_batch
|
||||
|
||||
# Add the last batch if it's not empty and we're not dropping it
|
||||
if batch and (not self.drop_last or len(batch) * max_len_in_batch == self.batch_size):
|
||||
batches.append(batch)
|
||||
|
||||
return iter(batches)
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return 1
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
|
||||
|
||||
class CustomDistributedBufferDynamicBatchSampler(DistributedSampler):
|
||||
def __init__(
|
||||
self,
|
||||
dataset,
|
||||
batch_size,
|
||||
batch_type="token",
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
rank_split=False,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
is_training: bool = True,
|
||||
sort_size: int = 1024,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
num_replicas = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
num_replicas = 1
|
||||
|
||||
# if rank_split:
|
||||
# logging.info(f"Warning, rank_split: {rank_split}, batch and shuffle data in local rank")
|
||||
# rank = 0
|
||||
# num_replicas = 1
|
||||
|
||||
self.rank = rank
|
||||
self.num_replicas = num_replicas
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
self.batch_type = batch_type
|
||||
self.is_training = is_training
|
||||
self.shuffle = shuffle and is_training
|
||||
self.drop_last = drop_last
|
||||
|
||||
self.total_size = len(self.dataset)
|
||||
self.num_samples = int(math.ceil(self.total_size / self.num_replicas))
|
||||
self.epoch = 0
|
||||
self.sort_size = sort_size * num_replicas
|
||||
self.max_token_length = kwargs.get("max_token_length", 2048)
|
||||
self.length_scale_source = kwargs.get("length_scale_source", 1.0)
|
||||
super().__init__(
|
||||
dataset, num_replicas=num_replicas, rank=rank, shuffle=shuffle, drop_last=drop_last
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
if self.shuffle:
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
random.seed(self.epoch)
|
||||
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset)))
|
||||
|
||||
# Create sorted buffers and form batches
|
||||
buffer_batches = []
|
||||
for i in range(0, len(indices), self.sort_size):
|
||||
buffer = sorted(
|
||||
indices[i : i + self.sort_size], key=lambda idx: self.dataset.get_source_len(idx)
|
||||
)
|
||||
batch = []
|
||||
max_len_in_batch = 0
|
||||
for idx in buffer:
|
||||
original_sample_length = self.dataset.get_source_len(idx)
|
||||
if original_sample_length > self.max_token_length:
|
||||
continue
|
||||
sample_length = 1 if self.batch_type == "example" else original_sample_length
|
||||
potential_batch_length = max(max_len_in_batch, sample_length) * (len(batch) + 1)
|
||||
if potential_batch_length <= self.batch_size:
|
||||
batch.append(idx)
|
||||
max_len_in_batch = max(max_len_in_batch, sample_length)
|
||||
else:
|
||||
buffer_batches.append(batch)
|
||||
batch = [idx]
|
||||
max_len_in_batch = sample_length
|
||||
if batch:
|
||||
buffer_batches.append(batch)
|
||||
|
||||
# Ensure each rank gets the same number of batches, duplicate data if needed
|
||||
batches_per_rank = math.ceil(len(buffer_batches) / self.num_replicas)
|
||||
total_batches_needed = batches_per_rank * self.num_replicas
|
||||
|
||||
extra_batches = total_batches_needed - len(buffer_batches)
|
||||
buffer_batches += random.choices(buffer_batches, k=extra_batches)
|
||||
|
||||
# Evenly distribute batches from buffer_batches to each rank
|
||||
rank_batches = [[] for _ in range(self.num_replicas)]
|
||||
for i, batch in enumerate(buffer_batches):
|
||||
rank_batches[i % self.num_replicas].append(batch)
|
||||
|
||||
# Assign all batches for the current rank directly
|
||||
final_batches = rank_batches[self.rank]
|
||||
|
||||
return iter(final_batches)
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return 1
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
|
||||
|
||||
class DistributedSamplerWarp(BatchSampler):
|
||||
def __init__(
|
||||
self, dataset, batch_size, num_replicas=None, rank=None, shuffle=True, drop_last=False
|
||||
):
|
||||
if num_replicas is None:
|
||||
if not torch.distributed.is_available():
|
||||
raise RuntimeError("Requires distributed package to be available")
|
||||
num_replicas = torch.distributed.get_world_size()
|
||||
if rank is None:
|
||||
if not torch.distributed.is_available():
|
||||
raise RuntimeError("Requires distributed package to be available")
|
||||
rank = torch.distributed.get_rank()
|
||||
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
self.num_replicas = num_replicas
|
||||
self.rank = rank
|
||||
self.shuffle = shuffle
|
||||
self.drop_last = drop_last
|
||||
|
||||
# Create an instance of the DistributedSampler
|
||||
self.sampler = DistributedSampler(
|
||||
self.dataset, num_replicas=self.num_replicas, rank=self.rank, shuffle=self.shuffle
|
||||
)
|
||||
|
||||
# Call BatchSampler's constructor
|
||||
super().__init__(self.sampler, batch_size, drop_last)
|
||||
|
||||
def __iter__(self):
|
||||
# If we shuffle, we need to call the set_epoch method
|
||||
if self.shuffle:
|
||||
self.sampler.set_epoch(self.epoch)
|
||||
|
||||
# Generate batch indices using the parent class
|
||||
return super().__iter__()
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
@@ -0,0 +1,123 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
import hydra
|
||||
from omegaconf import DictConfig, OmegaConf
|
||||
import concurrent.futures
|
||||
import librosa
|
||||
import torch.distributed as dist
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
def gen_jsonl_from_wav_text_list(
|
||||
path, data_type_list=("source", "target"), jsonl_file_out: str = None, **kwargs
|
||||
):
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
world_size = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
world_size = 1
|
||||
|
||||
cpu_cores = os.cpu_count() or 1
|
||||
print(f"convert wav.scp text to jsonl, ncpu: {cpu_cores}")
|
||||
if rank == 0:
|
||||
json_dict = {}
|
||||
for data_type, data_file in zip(data_type_list, path):
|
||||
json_dict[data_type] = {}
|
||||
with open(data_file, "r") as f:
|
||||
|
||||
data_file_lists = f.readlines()
|
||||
lines_for_each_th = (len(data_file_lists) - 1) // cpu_cores + 1
|
||||
task_num = cpu_cores if len(data_file_lists) > cpu_cores else 1
|
||||
# import pdb;pdb.set_trace()
|
||||
if task_num > 1:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=cpu_cores) as executor:
|
||||
|
||||
futures = [
|
||||
executor.submit(
|
||||
parse_context_length,
|
||||
data_file_lists[
|
||||
i * lines_for_each_th : (i + 1) * lines_for_each_th
|
||||
],
|
||||
data_type,
|
||||
i,
|
||||
)
|
||||
for i in range(task_num)
|
||||
]
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
|
||||
json_dict[data_type].update(future.result())
|
||||
else:
|
||||
res = parse_context_length(data_file_lists, data_type)
|
||||
json_dict[data_type].update(res)
|
||||
|
||||
with open(jsonl_file_out, "w") as f:
|
||||
for key in json_dict[data_type_list[0]].keys():
|
||||
jsonl_line = {"key": key}
|
||||
for data_file in data_type_list:
|
||||
jsonl_line.update(json_dict[data_file][key])
|
||||
jsonl_line = json.dumps(jsonl_line, ensure_ascii=False)
|
||||
f.write(jsonl_line + "\n")
|
||||
f.flush()
|
||||
print(f"processed {len(json_dict[data_type_list[0]])} samples")
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if world_size > 1:
|
||||
dist.barrier()
|
||||
|
||||
|
||||
def parse_context_length(data_list: list, data_type: str, id=0):
|
||||
pbar = tqdm(total=len(data_list), dynamic_ncols=True)
|
||||
res = {}
|
||||
for i, line in enumerate(data_list):
|
||||
pbar.update(1)
|
||||
pbar.set_description(f"cpu: {id}")
|
||||
lines = line.strip().split(maxsplit=1)
|
||||
key = lines[0]
|
||||
line = lines[1] if len(lines) > 1 else ""
|
||||
line = line.strip()
|
||||
if os.path.exists(line):
|
||||
waveform, _ = librosa.load(line, sr=16000)
|
||||
sample_num = len(waveform)
|
||||
context_len = int(sample_num / 16000 * 1000 / 10)
|
||||
else:
|
||||
context_len = len(line.split()) if " " in line else len(line)
|
||||
res[key] = {data_type: line, f"{data_type}_len": context_len}
|
||||
return res
|
||||
|
||||
|
||||
@hydra.main(config_name=None, version_base=None)
|
||||
def main_hydra(cfg: DictConfig):
|
||||
|
||||
kwargs = OmegaConf.to_container(cfg, resolve=True)
|
||||
print(kwargs)
|
||||
|
||||
scp_file_list = kwargs.get(
|
||||
"scp_file_list",
|
||||
("/Users/zhifu/funasr1.0/test_local/wav.scp", "/Users/zhifu/funasr1.0/test_local/text.txt"),
|
||||
)
|
||||
if isinstance(scp_file_list, str):
|
||||
scp_file_list = eval(scp_file_list)
|
||||
data_type_list = kwargs.get("data_type_list", ("source", "target"))
|
||||
jsonl_file_out = kwargs.get(
|
||||
"jsonl_file_out", "/Users/zhifu/funasr1.0/test_local/audio_datasets.jsonl"
|
||||
)
|
||||
gen_jsonl_from_wav_text_list(
|
||||
scp_file_list, data_type_list=data_type_list, jsonl_file_out=jsonl_file_out
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
python -m funasr.datasets.audio_datasets.scp2jsonl \
|
||||
++scp_file_list='["/Users/zhifu/funasr1.0/test_local/wav.scp", "/Users/zhifu/funasr1.0/test_local/text.txt"]' \
|
||||
++data_type_list='["source", "target"]' \
|
||||
++jsonl_file_out=/Users/zhifu/funasr1.0/test_local/audio_datasets.jsonl
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_hydra()
|
||||
@@ -0,0 +1,121 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
import hydra
|
||||
from omegaconf import DictConfig, OmegaConf
|
||||
import concurrent.futures
|
||||
import librosa
|
||||
import torch.distributed as dist
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
def gen_jsonl_from_wav_text_list(
|
||||
path, data_type_list=("source",), jsonl_file_out: str = None, **kwargs
|
||||
):
|
||||
try:
|
||||
rank = dist.get_rank()
|
||||
world_size = dist.get_world_size()
|
||||
except:
|
||||
rank = 0
|
||||
world_size = 1
|
||||
|
||||
cpu_cores = os.cpu_count() or 1
|
||||
print(f"convert wav.scp text to jsonl, ncpu: {cpu_cores}")
|
||||
if rank == 0:
|
||||
json_dict = {}
|
||||
# for data_type, data_file in zip(data_type_list, path):
|
||||
data_type = data_type_list[0]
|
||||
data_file = path
|
||||
json_dict[data_type] = {}
|
||||
with open(data_file, "r") as f:
|
||||
|
||||
data_file_lists = f.readlines()
|
||||
print("")
|
||||
lines_for_each_th = (len(data_file_lists) - 1) // cpu_cores + 1
|
||||
task_num = cpu_cores if len(data_file_lists) > cpu_cores else 1
|
||||
# import pdb;pdb.set_trace()
|
||||
if task_num > 1:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=cpu_cores) as executor:
|
||||
|
||||
futures = [
|
||||
executor.submit(
|
||||
parse_context_length,
|
||||
data_file_lists[i * lines_for_each_th : (i + 1) * lines_for_each_th],
|
||||
data_type,
|
||||
i,
|
||||
)
|
||||
for i in range(task_num)
|
||||
]
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
|
||||
json_dict[data_type].update(future.result())
|
||||
else:
|
||||
res = parse_context_length(data_file_lists, data_type)
|
||||
json_dict[data_type].update(res)
|
||||
|
||||
with open(jsonl_file_out, "w") as f:
|
||||
for key in json_dict[data_type_list[0]].keys():
|
||||
jsonl_line = {"key": key}
|
||||
for data_file in data_type_list:
|
||||
jsonl_line.update(json_dict[data_file][key])
|
||||
# jsonl_line = json.dumps(jsonl_line, ensure_ascii=False)
|
||||
source_len = jsonl_line["source_len"]
|
||||
jsonl_line = f"{key} {source_len}"
|
||||
f.write(jsonl_line + "\n")
|
||||
f.flush()
|
||||
print(f"processed {len(json_dict[data_type_list[0]])} samples")
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if world_size > 1:
|
||||
dist.barrier()
|
||||
|
||||
|
||||
def parse_context_length(data_list: list, data_type: str, id=0):
|
||||
pbar = tqdm(total=len(data_list), dynamic_ncols=True)
|
||||
res = {}
|
||||
for i, line in enumerate(data_list):
|
||||
pbar.update(1)
|
||||
pbar.set_description(f"cpu: {id}")
|
||||
lines = line.strip().split(maxsplit=1)
|
||||
key = lines[0]
|
||||
line = lines[1] if len(lines) > 1 else ""
|
||||
line = line.strip()
|
||||
if os.path.exists(line):
|
||||
waveform, _ = librosa.load(line, sr=16000)
|
||||
sample_num = len(waveform)
|
||||
context_len = int(sample_num / 16000 * 1000 / 10)
|
||||
else:
|
||||
context_len = len(line.split()) if " " in line else len(line)
|
||||
res[key] = {data_type: line, f"{data_type}_len": context_len}
|
||||
return res
|
||||
|
||||
|
||||
@hydra.main(config_name=None, version_base=None)
|
||||
def main_hydra(cfg: DictConfig):
|
||||
|
||||
kwargs = OmegaConf.to_container(cfg, resolve=True)
|
||||
print(kwargs)
|
||||
|
||||
scp_file_list = kwargs.get("scp_file_list", "/Users/zhifu/funasr1.0/data/list/train_wav.scp")
|
||||
# if isinstance(scp_file_list, str):
|
||||
# scp_file_list = eval(scp_file_list)
|
||||
data_type_list = kwargs.get("data_type_list", ("source",))
|
||||
jsonl_file_out = kwargs.get("jsonl_file_out", "/Users/zhifu/funasr1.0/data/list/wav_len.txt")
|
||||
gen_jsonl_from_wav_text_list(
|
||||
scp_file_list, data_type_list=data_type_list, jsonl_file_out=jsonl_file_out
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
python -m funasr.datasets.audio_datasets.scp2jsonl \
|
||||
++scp_file_list='["/Users/zhifu/funasr1.0/test_local/wav.scp", "/Users/zhifu/funasr1.0/test_local/text.txt"]' \
|
||||
++data_type_list='["source", "target"]' \
|
||||
++jsonl_file_out=/Users/zhifu/funasr1.0/test_local/audio_datasets.jsonl
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_hydra()
|
||||
@@ -0,0 +1,99 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
import hydra
|
||||
from omegaconf import DictConfig, OmegaConf
|
||||
import concurrent.futures
|
||||
import librosa
|
||||
import torch.distributed as dist
|
||||
import threading
|
||||
from tqdm import tqdm
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
|
||||
def gen_scp_from_jsonl(jsonl_file, jsonl_file_out, ncpu):
|
||||
jsonl_file_out_f = open(jsonl_file_out, "w")
|
||||
with open(jsonl_file, encoding="utf-8") as fin:
|
||||
lines = fin.readlines()
|
||||
|
||||
num_total = len(lines)
|
||||
if ncpu > 1:
|
||||
# 使用ThreadPoolExecutor限制并发线程数
|
||||
with ThreadPoolExecutor(max_workers=ncpu) as executor:
|
||||
# 提交任务到线程池
|
||||
futures = {executor.submit(update_data, lines, i) for i in tqdm(range(num_total))}
|
||||
|
||||
# 等待所有任务完成,这会阻塞直到所有提交的任务完成
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
# 这里可以添加额外的逻辑来处理完成的任务,但在这个例子中我们只是等待
|
||||
pass
|
||||
else:
|
||||
for i in range(num_total):
|
||||
update_data(lines, i)
|
||||
logging.info("All audio durations have been processed.")
|
||||
|
||||
for line in lines:
|
||||
|
||||
jsonl_file_out_f.write(line + "\n")
|
||||
jsonl_file_out_f.flush()
|
||||
|
||||
jsonl_file_out_f.close()
|
||||
|
||||
|
||||
def update_data(lines, i):
|
||||
line = lines[i]
|
||||
data = json.loads(line.strip())
|
||||
|
||||
wav_path = data["source"].replace("/cpfs01", "/cpfs_speech/data")
|
||||
if os.path.exists(wav_path):
|
||||
waveform, _ = librosa.load(wav_path, sr=16000)
|
||||
sample_num = len(waveform)
|
||||
source_len = int(sample_num / 16000 * 1000 / 10)
|
||||
source_len_old = data["source_len"]
|
||||
# if (source_len_old - source_len) > 100 or (source_len - source_len_old) > 100:
|
||||
# logging.info(f"old: {source_len_old}, new: {source_len}, wav: {wav_path}")
|
||||
data["source_len"] = source_len
|
||||
data["source"] = wav_path
|
||||
jsonl_line = json.dumps(data, ensure_ascii=False)
|
||||
lines[i] = jsonl_line
|
||||
|
||||
|
||||
def update_wav_len(jsonl_file_list_in, jsonl_file_out_dir, ncpu=1):
|
||||
|
||||
os.makedirs(jsonl_file_out_dir, exist_ok=True)
|
||||
with open(jsonl_file_list_in, "r") as f:
|
||||
data_file_lists = f.readlines()
|
||||
|
||||
for i, jsonl in enumerate(data_file_lists):
|
||||
filename_with_extension = os.path.basename(jsonl.strip())
|
||||
jsonl_file_out = os.path.join(jsonl_file_out_dir, filename_with_extension)
|
||||
logging.info(f"{i}/{len(data_file_lists)}, jsonl: {jsonl}, {jsonl_file_out}")
|
||||
|
||||
gen_scp_from_jsonl(jsonl.strip(), jsonl_file_out, ncpu)
|
||||
|
||||
|
||||
@hydra.main(config_name=None, version_base=None)
|
||||
def main_hydra(cfg: DictConfig):
|
||||
|
||||
kwargs = OmegaConf.to_container(cfg, resolve=True)
|
||||
logging.info(kwargs)
|
||||
|
||||
jsonl_file_list_in = kwargs.get(
|
||||
"jsonl_file_list_in", "/Users/zhifu/funasr1.0/data/list/data_jsonl.list"
|
||||
)
|
||||
jsonl_file_out_dir = kwargs.get("jsonl_file_out_dir", "/Users/zhifu/funasr1.0/data_tmp")
|
||||
ncpu = kwargs.get("ncpu", 1)
|
||||
update_wav_len(jsonl_file_list_in, jsonl_file_out_dir, ncpu)
|
||||
# gen_scp_from_jsonl(jsonl_file_list_in, jsonl_file_out_dir)
|
||||
|
||||
|
||||
"""
|
||||
python -m funasr.datasets.audio_datasets.json2scp \
|
||||
++scp_file_list='["/Users/zhifu/funasr1.0/test_local/wav.scp", "/Users/zhifu/funasr1.0/test_local/text.txt"]' \
|
||||
++data_type_list='["source", "target"]' \
|
||||
++jsonl_file_in=/Users/zhifu/funasr1.0/test_local/audio_datasets.jsonl
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_hydra()
|
||||
@@ -0,0 +1,136 @@
|
||||
import logging
|
||||
import torch
|
||||
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
# @tables.register("dataloader_classes", "DataloaderMapStyle")
|
||||
def DataloaderMapStyle(frontend=None, tokenizer=None, **kwargs):
|
||||
# dataset
|
||||
logging.info("Build dataloader")
|
||||
dataset_class = tables.dataset_classes.get(kwargs.get("dataset", "AudioDataset"))
|
||||
dataset_tr = dataset_class(
|
||||
kwargs.get("train_data_set_list"),
|
||||
frontend=frontend,
|
||||
tokenizer=tokenizer,
|
||||
is_training=True,
|
||||
**kwargs.get("dataset_conf"),
|
||||
)
|
||||
dataset_val = dataset_class(
|
||||
kwargs.get("valid_data_set_list"),
|
||||
frontend=frontend,
|
||||
tokenizer=tokenizer,
|
||||
is_training=False,
|
||||
**kwargs.get("dataset_conf"),
|
||||
)
|
||||
|
||||
# dataloader
|
||||
batch_sampler = kwargs["dataset_conf"].get("batch_sampler", "BatchSampler")
|
||||
batch_sampler_val = None
|
||||
if batch_sampler is not None:
|
||||
batch_sampler_class = tables.batch_sampler_classes.get(batch_sampler)
|
||||
batch_sampler = batch_sampler_class(dataset_tr, **kwargs.get("dataset_conf"))
|
||||
batch_sampler_val = batch_sampler_class(
|
||||
dataset_val, is_training=False, **kwargs.get("dataset_conf")
|
||||
)
|
||||
|
||||
dataloader_tr = torch.utils.data.DataLoader(
|
||||
dataset_tr, collate_fn=dataset_tr.collator, **batch_sampler
|
||||
)
|
||||
dataloader_val = torch.utils.data.DataLoader(
|
||||
dataset_val, collate_fn=dataset_val.collator, **batch_sampler_val
|
||||
)
|
||||
|
||||
return dataloader_tr, dataloader_val
|
||||
|
||||
|
||||
@tables.register("dataloader_classes", "DataloaderMapStyle")
|
||||
class DataloaderMapStyle:
|
||||
def __init__(self, frontend=None, tokenizer=None, **kwargs):
|
||||
# dataset
|
||||
logging.info("Build dataloader")
|
||||
dataset_class = tables.dataset_classes.get(kwargs.get("dataset", "AudioDataset"))
|
||||
dataset_tr = dataset_class(
|
||||
kwargs.get("train_data_set_list"),
|
||||
frontend=frontend,
|
||||
tokenizer=tokenizer,
|
||||
is_training=True,
|
||||
**kwargs.get("dataset_conf"),
|
||||
)
|
||||
dataset_val = dataset_class(
|
||||
kwargs.get("valid_data_set_list"),
|
||||
frontend=frontend,
|
||||
tokenizer=tokenizer,
|
||||
is_training=False,
|
||||
**kwargs.get("dataset_conf"),
|
||||
)
|
||||
|
||||
self.dataset_tr = dataset_tr
|
||||
self.dataset_val = dataset_val
|
||||
self.kwargs = kwargs
|
||||
|
||||
# split dataset
|
||||
self.data_split_num = kwargs["dataset_conf"].get("data_split_num", 1)
|
||||
self.dataset_class = dataset_class
|
||||
self.frontend = frontend
|
||||
self.tokenizer = tokenizer
|
||||
self.kwargs = kwargs
|
||||
|
||||
def build_iter(self, epoch=0, data_split_i=0, start_step=0, **kwargs):
|
||||
|
||||
# reload dataset slice
|
||||
if self.data_split_num > 1:
|
||||
del self.dataset_tr
|
||||
self.dataset_tr = self.dataset_class(
|
||||
self.kwargs.get("train_data_set_list"),
|
||||
frontend=self.frontend,
|
||||
tokenizer=self.tokenizer,
|
||||
is_training=True,
|
||||
**self.kwargs.get("dataset_conf"),
|
||||
data_split_i=data_split_i,
|
||||
)
|
||||
|
||||
# dataloader
|
||||
batch_sampler = self.kwargs["dataset_conf"].get("batch_sampler", "BatchSampler")
|
||||
batch_sampler_val = None
|
||||
if batch_sampler is not None:
|
||||
batch_sampler_class = tables.batch_sampler_classes.get(batch_sampler)
|
||||
batch_sampler = batch_sampler_class(
|
||||
self.dataset_tr, start_step=start_step, **self.kwargs.get("dataset_conf")
|
||||
)
|
||||
batch_sampler_val = batch_sampler_class(
|
||||
self.dataset_val, is_training=False, **self.kwargs.get("dataset_conf")
|
||||
)
|
||||
|
||||
batch_sampler["batch_sampler"].set_epoch(epoch)
|
||||
batch_sampler_val["batch_sampler"].set_epoch(epoch)
|
||||
dataloader_tr = torch.utils.data.DataLoader(
|
||||
self.dataset_tr, collate_fn=self.dataset_tr.collator, **batch_sampler
|
||||
)
|
||||
dataloader_val = torch.utils.data.DataLoader(
|
||||
self.dataset_val, collate_fn=self.dataset_val.collator, **batch_sampler_val
|
||||
)
|
||||
|
||||
return dataloader_tr, dataloader_val
|
||||
|
||||
|
||||
@tables.register("dataloader_classes", "DataloaderIterable")
|
||||
def DataloaderIterable(frontend=None, tokenizer=None, **kwargs):
|
||||
logging.info("Build dataloader")
|
||||
dataset_class = tables.dataset_classes.get(kwargs.get("dataset", "LargeDataset"))
|
||||
dataset_tr = dataset_class(
|
||||
kwargs.get("train_data_set_list"),
|
||||
frontend=frontend,
|
||||
tokenizer=tokenizer,
|
||||
is_training=True,
|
||||
**kwargs.get("dataset_conf"),
|
||||
)
|
||||
dataset_val = dataset_class(
|
||||
kwargs.get("valid_data_set_list"),
|
||||
frontend=frontend,
|
||||
tokenizer=tokenizer,
|
||||
is_training=False,
|
||||
**kwargs.get("dataset_conf"),
|
||||
)
|
||||
|
||||
return dataset_tr, dataset_val
|
||||
Whitespace-only changes.
@@ -0,0 +1,9 @@
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
class AbsIterFactory(ABC):
|
||||
@abstractmethod
|
||||
def build_iter(self, epoch: int, shuffle: bool = None) -> Iterator:
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,109 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Union
|
||||
|
||||
import sentencepiece as spm
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from funasr.datasets.large_datasets.dataset import Dataset
|
||||
from funasr.datasets.large_datasets.abs_iter_factory import AbsIterFactory
|
||||
from funasr.tokenizer.abs_tokenizer import AbsTokenizer
|
||||
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
def read_symbol_table(symbol_table_file):
|
||||
if isinstance(symbol_table_file, str):
|
||||
symbol_table = {}
|
||||
with open(symbol_table_file, "r", encoding="utf8") as fin:
|
||||
for i, line in enumerate(fin):
|
||||
char = line.strip()
|
||||
symbol_table[char] = i
|
||||
else:
|
||||
assert isinstance(symbol_table_file, list)
|
||||
symbol_table = {}
|
||||
for i, char in enumerate(symbol_table_file):
|
||||
symbol_table[char] = i
|
||||
return symbol_table
|
||||
|
||||
|
||||
def load_seg_dict(seg_dict_file):
|
||||
seg_dict = {}
|
||||
assert isinstance(seg_dict_file, str)
|
||||
with open(seg_dict_file, "r", encoding="utf8") as f:
|
||||
lines = f.readlines()
|
||||
for line in lines:
|
||||
s = line.strip().split()
|
||||
key = s[0]
|
||||
value = s[1:]
|
||||
seg_dict[key] = " ".join(value)
|
||||
return seg_dict
|
||||
|
||||
|
||||
class SentencepiecesTokenizer(AbsTokenizer):
|
||||
def __init__(self, model: Union[Path, str]):
|
||||
self.model = str(model)
|
||||
self.sp = None
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__.__name__}(model="{self.model}")'
|
||||
|
||||
def _build_sentence_piece_processor(self):
|
||||
if self.sp is None:
|
||||
self.sp = spm.SentencePieceProcessor()
|
||||
self.sp.load(self.model)
|
||||
|
||||
def text2tokens(self, line: str) -> List[str]:
|
||||
self._build_sentence_piece_processor()
|
||||
return self.sp.EncodeAsPieces(line)
|
||||
|
||||
def tokens2text(self, tokens: Iterable[str]) -> str:
|
||||
self._build_sentence_piece_processor()
|
||||
return self.sp.DecodePieces(list(tokens))
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "LargeDataset")
|
||||
class LargeDataLoader(AbsIterFactory):
|
||||
def __init__(self, args, mode="train"):
|
||||
symbol_table, seg_dict, punc_dict, bpe_tokenizer = None, None, None, None
|
||||
if hasattr(args, "token_list") and args.token_list is not None:
|
||||
symbol_table = read_symbol_table(args.token_list)
|
||||
if hasattr(args, "seg_dict_file") and args.seg_dict_file is not None:
|
||||
seg_dict = load_seg_dict(args.seg_dict_file)
|
||||
if hasattr(args, "punc_list") and args.punc_list is not None:
|
||||
punc_dict = read_symbol_table(args.punc_list)
|
||||
if hasattr(args, "bpemodel") and args.bpemodel is not None:
|
||||
bpe_tokenizer = SentencepiecesTokenizer(args.bpemodel)
|
||||
self.dataset_conf = args.dataset_conf
|
||||
if "frontend_conf" not in args:
|
||||
self.frontend_conf = None
|
||||
else:
|
||||
self.frontend_conf = args.frontend_conf
|
||||
self.speed_perturb = args.speed_perturb if hasattr(args, "speed_perturb") else None
|
||||
logging.info("dataloader config: {}".format(self.dataset_conf))
|
||||
batch_mode = self.dataset_conf.get("batch_mode", "padding")
|
||||
data_list = args.train_data_file if mode == "train" else args.valid_data_file
|
||||
self.dataset = Dataset(
|
||||
data_list,
|
||||
symbol_table,
|
||||
seg_dict,
|
||||
punc_dict,
|
||||
bpe_tokenizer,
|
||||
self.dataset_conf,
|
||||
self.frontend_conf,
|
||||
speed_perturb=self.speed_perturb if mode == "train" else None,
|
||||
mode=mode,
|
||||
batch_mode=batch_mode,
|
||||
)
|
||||
|
||||
def build_iter(self, epoch, shuffle=True):
|
||||
self.dataset.set_epoch(epoch)
|
||||
data_loader = DataLoader(
|
||||
self.dataset,
|
||||
batch_size=None,
|
||||
pin_memory=True,
|
||||
num_workers=self.dataset_conf.get("num_workers", 8),
|
||||
)
|
||||
return data_loader
|
||||
@@ -0,0 +1,194 @@
|
||||
from typing import Collection
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from funasr.models.transformer.utils.nets_utils import pad_list, pad_list_all_dim
|
||||
|
||||
|
||||
class CommonCollateFn:
|
||||
"""Functor class of common_collate_fn()"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
float_pad_value: Union[float, int] = 0.0,
|
||||
int_pad_value: int = -32768,
|
||||
not_sequence: Collection[str] = (),
|
||||
max_sample_size=None,
|
||||
):
|
||||
self.float_pad_value = float_pad_value
|
||||
self.int_pad_value = int_pad_value
|
||||
self.not_sequence = set(not_sequence)
|
||||
self.max_sample_size = max_sample_size
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"{self.__class__}(float_pad_value={self.float_pad_value}, "
|
||||
f"int_pad_value={self.float_pad_value})"
|
||||
)
|
||||
|
||||
def __call__(
|
||||
self, data: Collection[Tuple[str, Dict[str, np.ndarray]]]
|
||||
) -> Tuple[List[str], Dict[str, torch.Tensor]]:
|
||||
return common_collate_fn(
|
||||
data,
|
||||
float_pad_value=self.float_pad_value,
|
||||
int_pad_value=self.int_pad_value,
|
||||
not_sequence=self.not_sequence,
|
||||
)
|
||||
|
||||
|
||||
def common_collate_fn(
|
||||
data: Collection[Tuple[str, Dict[str, np.ndarray]]],
|
||||
float_pad_value: Union[float, int] = 0.0,
|
||||
int_pad_value: int = -32768,
|
||||
not_sequence: Collection[str] = (),
|
||||
) -> Tuple[List[str], Dict[str, torch.Tensor]]:
|
||||
"""Concatenate ndarray-list to an array and convert to torch.Tensor."""
|
||||
uttids = [u for u, _ in data]
|
||||
data = [d for _, d in data]
|
||||
|
||||
assert all(set(data[0]) == set(d) for d in data), "dict-keys mismatching"
|
||||
assert all(
|
||||
not k.endswith("_lengths") for k in data[0]
|
||||
), f"*_lengths is reserved: {list(data[0])}"
|
||||
|
||||
output = {}
|
||||
for key in data[0]:
|
||||
if data[0][key].dtype.kind == "i":
|
||||
pad_value = int_pad_value
|
||||
else:
|
||||
pad_value = float_pad_value
|
||||
|
||||
array_list = [d[key] for d in data]
|
||||
tensor_list = [torch.from_numpy(a) for a in array_list]
|
||||
tensor = pad_list(tensor_list, pad_value)
|
||||
output[key] = tensor
|
||||
|
||||
if key not in not_sequence:
|
||||
lens = torch.tensor([d[key].shape[0] for d in data], dtype=torch.long)
|
||||
output[key + "_lengths"] = lens
|
||||
|
||||
output = (uttids, output)
|
||||
return output
|
||||
|
||||
|
||||
class DiarCollateFn:
|
||||
"""Functor class of common_collate_fn()"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
float_pad_value: Union[float, int] = 0.0,
|
||||
int_pad_value: int = -32768,
|
||||
not_sequence: Collection[str] = (),
|
||||
max_sample_size=None,
|
||||
):
|
||||
self.float_pad_value = float_pad_value
|
||||
self.int_pad_value = int_pad_value
|
||||
self.not_sequence = set(not_sequence)
|
||||
self.max_sample_size = max_sample_size
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"{self.__class__}(float_pad_value={self.float_pad_value}, "
|
||||
f"int_pad_value={self.float_pad_value})"
|
||||
)
|
||||
|
||||
def __call__(
|
||||
self, data: Collection[Tuple[str, Dict[str, np.ndarray]]]
|
||||
) -> Tuple[List[str], Dict[str, torch.Tensor]]:
|
||||
return diar_collate_fn(
|
||||
data,
|
||||
float_pad_value=self.float_pad_value,
|
||||
int_pad_value=self.int_pad_value,
|
||||
not_sequence=self.not_sequence,
|
||||
)
|
||||
|
||||
|
||||
def diar_collate_fn(
|
||||
data: Collection[Tuple[str, Dict[str, np.ndarray]]],
|
||||
float_pad_value: Union[float, int] = 0.0,
|
||||
int_pad_value: int = -32768,
|
||||
not_sequence: Collection[str] = (),
|
||||
) -> Tuple[List[str], Dict[str, torch.Tensor]]:
|
||||
"""Concatenate ndarray-list to an array and convert to torch.Tensor."""
|
||||
uttids = [u for u, _ in data]
|
||||
data = [d for _, d in data]
|
||||
|
||||
assert all(set(data[0]) == set(d) for d in data), "dict-keys mismatching"
|
||||
assert all(
|
||||
not k.endswith("_lengths") for k in data[0]
|
||||
), f"*_lengths is reserved: {list(data[0])}"
|
||||
|
||||
output = {}
|
||||
for key in data[0]:
|
||||
if data[0][key].dtype.kind == "i":
|
||||
pad_value = int_pad_value
|
||||
else:
|
||||
pad_value = float_pad_value
|
||||
|
||||
array_list = [d[key] for d in data]
|
||||
tensor_list = [torch.from_numpy(a) for a in array_list]
|
||||
tensor = pad_list_all_dim(tensor_list, pad_value)
|
||||
output[key] = tensor
|
||||
|
||||
if key not in not_sequence:
|
||||
lens = torch.tensor([d[key].shape[0] for d in data], dtype=torch.long)
|
||||
output[key + "_lengths"] = lens
|
||||
|
||||
output = (uttids, output)
|
||||
return output
|
||||
|
||||
|
||||
def crop_to_max_size(feature, target_size):
|
||||
size = len(feature)
|
||||
diff = size - target_size
|
||||
if diff <= 0:
|
||||
return feature
|
||||
|
||||
start = np.random.randint(0, diff + 1)
|
||||
end = size - diff + start
|
||||
return feature[start:end]
|
||||
|
||||
|
||||
def clipping_collate_fn(
|
||||
data: Collection[Tuple[str, Dict[str, np.ndarray]]],
|
||||
max_sample_size=None,
|
||||
not_sequence: Collection[str] = (),
|
||||
) -> Tuple[List[str], Dict[str, torch.Tensor]]:
|
||||
# mainly for pre-training
|
||||
uttids = [u for u, _ in data]
|
||||
data = [d for _, d in data]
|
||||
|
||||
assert all(set(data[0]) == set(d) for d in data), "dict-keys mismatching"
|
||||
assert all(
|
||||
not k.endswith("_lengths") for k in data[0]
|
||||
), f"*_lengths is reserved: {list(data[0])}"
|
||||
|
||||
output = {}
|
||||
for key in data[0]:
|
||||
array_list = [d[key] for d in data]
|
||||
tensor_list = [torch.from_numpy(a) for a in array_list]
|
||||
sizes = [len(s) for s in tensor_list]
|
||||
if max_sample_size is None:
|
||||
target_size = min(sizes)
|
||||
else:
|
||||
target_size = min(min(sizes), max_sample_size)
|
||||
tensor = tensor_list[0].new_zeros(len(tensor_list), target_size, tensor_list[0].shape[1])
|
||||
for i, (source, size) in enumerate(zip(tensor_list, sizes)):
|
||||
diff = size - target_size
|
||||
if diff == 0:
|
||||
tensor[i] = source
|
||||
else:
|
||||
tensor[i] = crop_to_max_size(source, target_size)
|
||||
output[key] = tensor
|
||||
|
||||
if key not in not_sequence:
|
||||
lens = torch.tensor([source.shape[0] for source in tensor], dtype=torch.long)
|
||||
output[key + "_lengths"] = lens
|
||||
|
||||
output = (uttids, output)
|
||||
return output
|
||||
Whitespace-only changes.
@@ -0,0 +1,213 @@
|
||||
import random
|
||||
|
||||
from itertools import count
|
||||
from functools import partial
|
||||
from torch.utils.data import IterableDataset
|
||||
from funasr.datasets.large_datasets.datapipes.map import MapperIterDataPipe
|
||||
|
||||
tiebreaker = count()
|
||||
|
||||
|
||||
def _default_len_fn(token):
|
||||
return len(token), next(tiebreaker)
|
||||
|
||||
|
||||
def _token_len_fn(token, len_fn):
|
||||
return len_fn(token), next(tiebreaker), token
|
||||
|
||||
|
||||
class MaxTokenBucketizerIterDataPipe(IterableDataset):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
datapipe,
|
||||
batch_size=8000,
|
||||
len_fn=_default_len_fn,
|
||||
buffer_size=10240,
|
||||
sort_size=500,
|
||||
batch_mode="padding",
|
||||
):
|
||||
assert batch_size > 0, "Batch size is required to be larger than 0!"
|
||||
assert buffer_size >= -1, "Buffer size is required to be larger than -1!"
|
||||
assert sort_size > 0, "Sort size is required to be larger than 0!"
|
||||
|
||||
datapipe = MapperIterDataPipe(datapipe, fn=partial(_token_len_fn, len_fn=len_fn))
|
||||
self.datapipe = datapipe
|
||||
self.batch_size = batch_size
|
||||
self.buffer_size = buffer_size
|
||||
self.sort_size = sort_size
|
||||
self.batch_mode = batch_mode
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.datapipe.set_epoch(epoch)
|
||||
|
||||
def __iter__(self):
|
||||
buffer = []
|
||||
batch = []
|
||||
bucket = []
|
||||
max_lengths = 0
|
||||
min_lengths = 999999
|
||||
batch_lengths = 0
|
||||
|
||||
if self.batch_mode == "clipping":
|
||||
assert self.buffer_size > 0, "for clipping batch_mode, buffer_size must be > 1"
|
||||
for d in self.datapipe:
|
||||
if d[0] > self.batch_size:
|
||||
continue
|
||||
buffer.append(d)
|
||||
if len(buffer) == self.buffer_size:
|
||||
random.shuffle(buffer)
|
||||
for sample in buffer:
|
||||
bucket.append(sample)
|
||||
if len(bucket) == self.sort_size:
|
||||
bucket.sort()
|
||||
for x in bucket:
|
||||
length, _, token = x
|
||||
if length < min_lengths:
|
||||
min_lengths = length
|
||||
batch_lengths = min_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
min_lengths = length
|
||||
batch.append(token)
|
||||
bucket = []
|
||||
buffer = []
|
||||
|
||||
if buffer:
|
||||
random.shuffle(buffer)
|
||||
for sample in buffer:
|
||||
bucket.append(sample)
|
||||
if len(bucket) == self.sort_size:
|
||||
bucket.sort()
|
||||
for x in bucket:
|
||||
length, _, token = x
|
||||
if length < min_lengths:
|
||||
min_lengths = length
|
||||
batch_lengths = min_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
min_lengths = length
|
||||
batch.append(token)
|
||||
bucket = []
|
||||
buffer = []
|
||||
|
||||
if bucket:
|
||||
bucket.sort()
|
||||
for x in bucket:
|
||||
length, _, token = x
|
||||
if length < min_lengths:
|
||||
min_lengths = length
|
||||
batch_lengths = min_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
min_lengths = length
|
||||
batch.append(token)
|
||||
bucket = []
|
||||
|
||||
if batch:
|
||||
yield batch
|
||||
|
||||
else:
|
||||
if self.buffer_size == -1:
|
||||
for d in self.datapipe:
|
||||
if d[0] > self.batch_size:
|
||||
continue
|
||||
buffer.append(d)
|
||||
buffer.sort()
|
||||
for sample in buffer:
|
||||
length, _, token = sample
|
||||
if length > max_lengths:
|
||||
max_lengths = length
|
||||
batch_lengths = max_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
bucket.append(batch)
|
||||
batch = []
|
||||
max_lengths = length
|
||||
batch.append(token)
|
||||
random.shuffle(bucket)
|
||||
if bucket:
|
||||
for batch_sample in bucket:
|
||||
yield batch_sample
|
||||
if batch:
|
||||
yield batch
|
||||
|
||||
elif self.buffer_size == 0:
|
||||
for d in self.datapipe:
|
||||
if d[0] > self.batch_size:
|
||||
continue
|
||||
length, _, token = d
|
||||
if length > self.batch_size:
|
||||
continue
|
||||
if length > max_lengths:
|
||||
max_lengths = length
|
||||
batch_lengths = max_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
max_lengths = length
|
||||
batch.append(token)
|
||||
if batch:
|
||||
yield batch
|
||||
|
||||
else:
|
||||
for d in self.datapipe:
|
||||
if d[0] > self.batch_size:
|
||||
continue
|
||||
buffer.append(d)
|
||||
if len(buffer) == self.buffer_size:
|
||||
random.shuffle(buffer)
|
||||
for sample in buffer:
|
||||
bucket.append(sample)
|
||||
if len(bucket) == self.sort_size:
|
||||
bucket.sort()
|
||||
for x in bucket:
|
||||
length, _, token = x
|
||||
if length > max_lengths:
|
||||
max_lengths = length
|
||||
batch_lengths = max_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
max_lengths = length
|
||||
batch.append(token)
|
||||
bucket = []
|
||||
buffer = []
|
||||
|
||||
if buffer:
|
||||
random.shuffle(buffer)
|
||||
for sample in buffer:
|
||||
bucket.append(sample)
|
||||
if len(bucket) == self.sort_size:
|
||||
bucket.sort()
|
||||
for x in bucket:
|
||||
length, _, token = x
|
||||
if length > max_lengths:
|
||||
max_lengths = length
|
||||
batch_lengths = max_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
max_lengths = length
|
||||
batch.append(token)
|
||||
bucket = []
|
||||
buffer = []
|
||||
|
||||
if bucket:
|
||||
bucket.sort()
|
||||
for x in bucket:
|
||||
length, _, token = x
|
||||
if length > max_lengths:
|
||||
max_lengths = length
|
||||
batch_lengths = max_lengths * (len(batch) + 1)
|
||||
if batch_lengths > self.batch_size:
|
||||
yield batch
|
||||
batch = []
|
||||
max_lengths = length
|
||||
batch.append(token)
|
||||
bucket = []
|
||||
|
||||
if batch:
|
||||
yield batch
|
||||
@@ -0,0 +1,23 @@
|
||||
from torch.utils.data import IterableDataset
|
||||
|
||||
|
||||
def default_fn(data):
|
||||
return data
|
||||
|
||||
|
||||
class FilterIterDataPipe(IterableDataset):
|
||||
|
||||
def __init__(self, datapipe, fn=default_fn):
|
||||
self.datapipe = datapipe
|
||||
self.fn = fn
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.datapipe.set_epoch(epoch)
|
||||
|
||||
def __iter__(self):
|
||||
assert callable(self.fn)
|
||||
for data in self.datapipe:
|
||||
if self.fn(data):
|
||||
yield data
|
||||
else:
|
||||
continue
|
||||
@@ -0,0 +1,20 @@
|
||||
from torch.utils.data import IterableDataset
|
||||
|
||||
|
||||
def default_fn(data):
|
||||
return data
|
||||
|
||||
|
||||
class MapperIterDataPipe(IterableDataset):
|
||||
|
||||
def __init__(self, datapipe, fn=default_fn):
|
||||
self.datapipe = datapipe
|
||||
self.fn = fn
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.datapipe.set_epoch(epoch)
|
||||
|
||||
def __iter__(self):
|
||||
assert callable(self.fn)
|
||||
for data in self.datapipe:
|
||||
yield self.fn(data)
|
||||
@@ -0,0 +1,299 @@
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
from functools import partial
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
import torchaudio
|
||||
import numpy as np
|
||||
|
||||
# import librosa
|
||||
import librosa
|
||||
from kaldiio import ReadHelper
|
||||
from torch.utils.data import IterableDataset
|
||||
|
||||
from funasr.datasets.large_datasets.datapipes.batch import MaxTokenBucketizerIterDataPipe
|
||||
from funasr.datasets.large_datasets.datapipes.filter import FilterIterDataPipe
|
||||
from funasr.datasets.large_datasets.datapipes.map import MapperIterDataPipe
|
||||
from funasr.datasets.large_datasets.utils.clipping import clipping
|
||||
from funasr.datasets.large_datasets.utils.filter import filter
|
||||
from funasr.datasets.large_datasets.utils.padding import padding
|
||||
from funasr.datasets.large_datasets.utils.tokenize import tokenize
|
||||
|
||||
|
||||
def read_lists(list_file):
|
||||
lists = []
|
||||
with open(list_file, "r", encoding="utf8") as fin:
|
||||
for line in fin:
|
||||
parts = line.strip()
|
||||
lists.append(parts)
|
||||
return lists
|
||||
|
||||
|
||||
class AudioDataset(IterableDataset):
|
||||
def __init__(
|
||||
self,
|
||||
scp_lists,
|
||||
data_names,
|
||||
data_types,
|
||||
frontend_conf=None,
|
||||
shuffle=True,
|
||||
speed_perturb=None,
|
||||
mode="train",
|
||||
):
|
||||
self.scp_lists = scp_lists
|
||||
self.data_names = data_names
|
||||
self.data_types = data_types
|
||||
self.frontend_conf = frontend_conf
|
||||
self.shuffle = shuffle
|
||||
self.mode = mode
|
||||
self.epoch = -1
|
||||
self.rank = 0
|
||||
self.world_size = 1
|
||||
self.worker_id = 0
|
||||
self.num_workers = 1
|
||||
self.speed_perturb = speed_perturb
|
||||
if self.speed_perturb is not None:
|
||||
logging.info("Using speed_perturb: {}".format(speed_perturb))
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.epoch = epoch
|
||||
|
||||
def get_rank_data_list(self, data_index):
|
||||
assert dist.is_available()
|
||||
if dist.is_initialized():
|
||||
self.rank = dist.get_rank()
|
||||
self.world_size = dist.get_world_size()
|
||||
else:
|
||||
self.rank = 0
|
||||
self.world_size = 1
|
||||
|
||||
if self.mode == "train":
|
||||
if self.shuffle:
|
||||
random.seed(self.epoch)
|
||||
random.shuffle(data_index)
|
||||
return data_index[self.rank :: self.world_size]
|
||||
|
||||
return data_index
|
||||
|
||||
def get_worker_data_list(self, rank_data_index):
|
||||
worker_info = torch.utils.data.get_worker_info()
|
||||
if worker_info is None:
|
||||
self.worker_id = 0
|
||||
self.num_workers = 1
|
||||
else:
|
||||
self.worker_id = worker_info.id
|
||||
self.num_workers = worker_info.num_workers
|
||||
|
||||
return rank_data_index[self.worker_id :: self.num_workers]
|
||||
|
||||
def close_reader(self, reader_list):
|
||||
for reader in reader_list:
|
||||
reader.close()
|
||||
|
||||
def __iter__(self):
|
||||
data_index = list(range(len(self.scp_lists)))
|
||||
rank_data_index = self.get_rank_data_list(data_index)
|
||||
worker_data_index = self.get_worker_data_list(rank_data_index)
|
||||
|
||||
for index in worker_data_index:
|
||||
data = dict(scp=self.scp_lists[index])
|
||||
|
||||
assert "scp" in data
|
||||
scp = data["scp"]
|
||||
data_file_list = scp.strip().split()
|
||||
data_name_list = self.data_names.split(",")
|
||||
data_type_list = self.data_types.split(",")
|
||||
|
||||
for file in data_file_list:
|
||||
assert os.path.exists(file), "{} not exists".format(file)
|
||||
|
||||
assert (
|
||||
len(data_file_list) == len(data_name_list) == len(data_type_list)
|
||||
), "The item number of data, data_names, data_types must be the same "
|
||||
|
||||
reader_list = []
|
||||
for data_file, data_type in zip(data_file_list, data_type_list):
|
||||
if data_type == "kaldi_ark":
|
||||
ark_reader = ReadHelper("ark:{}".format(data_file))
|
||||
reader_list.append(ark_reader)
|
||||
elif data_type == "text" or data_type == "sound" or data_type == "text_hotword":
|
||||
text_reader = open(data_file, "r", encoding="utf-8")
|
||||
reader_list.append(text_reader)
|
||||
elif data_type == "none":
|
||||
continue
|
||||
else:
|
||||
raise TypeError("Data type {} is not supported".format(data_type))
|
||||
|
||||
for items in zip(*reader_list):
|
||||
sample_dict = {}
|
||||
for item, (data_name, data_type) in zip(items, zip(data_name_list, data_type_list)):
|
||||
if data_type == "kaldi_ark":
|
||||
key, mat = item
|
||||
sample_dict[data_name] = mat
|
||||
if data_name == "speech":
|
||||
sample_dict["key"] = key
|
||||
elif data_type == "sound":
|
||||
key, path = item.strip().split()
|
||||
try:
|
||||
waveform, sampling_rate = torchaudio.load(path)
|
||||
except:
|
||||
# waveform, sampling_rate = librosa.load(path, dtype='float32')
|
||||
waveform, sampling_rate = librosa.load(path, dtype="float32")
|
||||
if waveform.ndim == 2:
|
||||
waveform = waveform[:, 0]
|
||||
waveform = np.expand_dims(waveform, axis=0)
|
||||
waveform = torch.tensor(waveform)
|
||||
if self.frontend_conf is not None:
|
||||
if sampling_rate != self.frontend_conf["fs"]:
|
||||
waveform = torchaudio.transforms.Resample(
|
||||
orig_freq=sampling_rate, new_freq=self.frontend_conf["fs"]
|
||||
)(waveform)
|
||||
sampling_rate = self.frontend_conf["fs"]
|
||||
waveform = waveform.numpy()
|
||||
mat = waveform[0]
|
||||
if self.speed_perturb is not None:
|
||||
speed = random.choice(self.speed_perturb)
|
||||
if speed != 1.0:
|
||||
mat, _ = torchaudio.sox_effects.apply_effects_tensor(
|
||||
torch.tensor(mat).view(1, -1),
|
||||
sampling_rate,
|
||||
[["speed", str(speed)], ["rate", str(sampling_rate)]],
|
||||
)
|
||||
mat = mat.view(-1).numpy()
|
||||
sample_dict[data_name] = mat
|
||||
sample_dict["sampling_rate"] = sampling_rate
|
||||
if data_name == "speech":
|
||||
sample_dict["key"] = key
|
||||
elif data_type == "text_hotword":
|
||||
text = item
|
||||
segs = text.strip().split()
|
||||
sample_dict[data_name] = segs[1:]
|
||||
if "key" not in sample_dict:
|
||||
sample_dict["key"] = segs[0]
|
||||
sample_dict["hw_tag"] = 1
|
||||
elif data_type == "text_nospace":
|
||||
text = item
|
||||
segs = text.strip().split(maxsplit=1)
|
||||
sample_dict[data_name] = [x for x in segs[1]]
|
||||
if "key" not in sample_dict:
|
||||
sample_dict["key"] = segs[0]
|
||||
else:
|
||||
text = item
|
||||
segs = text.strip().split()
|
||||
sample_dict[data_name] = segs[1:]
|
||||
if "key" not in sample_dict:
|
||||
sample_dict["key"] = segs[0]
|
||||
yield sample_dict
|
||||
|
||||
self.close_reader(reader_list)
|
||||
|
||||
|
||||
def len_fn_example(data):
|
||||
return 1
|
||||
|
||||
|
||||
def len_fn_token(data):
|
||||
assert "speech" in data
|
||||
if "sampling_rate" in data:
|
||||
return (data["speech"].shape[0] / data["sampling_rate"]) * 1000.0
|
||||
else:
|
||||
return data["speech"].shape[0]
|
||||
|
||||
|
||||
def Dataset(
|
||||
data_list_file,
|
||||
dict,
|
||||
seg_dict,
|
||||
punc_dict,
|
||||
bpe_tokenizer,
|
||||
conf,
|
||||
frontend_conf,
|
||||
speed_perturb=None,
|
||||
mode="train",
|
||||
batch_mode="padding",
|
||||
):
|
||||
scp_lists = read_lists(data_list_file)
|
||||
shuffle = conf.get("shuffle", True)
|
||||
data_names = conf.get("data_names", "speech,text")
|
||||
data_types = conf.get("data_types", "kaldi_ark,text")
|
||||
|
||||
pre_hwfile = conf.get("pre_hwlist", None)
|
||||
# pre_prob = conf.get("pre_prob", 0) # unused yet
|
||||
if pre_hwfile is not None:
|
||||
pre_hwlist = []
|
||||
with open(pre_hwfile, "r", encoding="utf-8") as fin:
|
||||
for line in fin.readlines():
|
||||
pre_hwlist.append(line.strip())
|
||||
else:
|
||||
pre_hwlist = None
|
||||
|
||||
hw_config = {
|
||||
"sample_rate": conf.get("sample_rate", 0.6),
|
||||
"double_rate": conf.get("double_rate", 0.1),
|
||||
"hotword_min_length": conf.get("hotword_min_length", 2),
|
||||
"hotword_max_length": conf.get("hotword_max_length", 8),
|
||||
"pre_prob": conf.get("pre_prob", 0.0),
|
||||
"pre_hwlist": pre_hwlist,
|
||||
}
|
||||
|
||||
dataset = AudioDataset(
|
||||
scp_lists,
|
||||
data_names,
|
||||
data_types,
|
||||
frontend_conf=frontend_conf,
|
||||
shuffle=shuffle,
|
||||
speed_perturb=speed_perturb,
|
||||
mode=mode,
|
||||
)
|
||||
|
||||
if "text" in data_names:
|
||||
vocab = {
|
||||
"vocab": dict,
|
||||
"seg_dict": seg_dict,
|
||||
"punc_dict": punc_dict,
|
||||
"bpe_tokenizer": bpe_tokenizer,
|
||||
"hw_config": hw_config,
|
||||
}
|
||||
tokenize_fn = partial(tokenize, **vocab)
|
||||
dataset = MapperIterDataPipe(dataset, fn=tokenize_fn)
|
||||
|
||||
filter_conf = conf.get("filter_conf", {})
|
||||
filter_fn = partial(filter, **filter_conf)
|
||||
dataset = FilterIterDataPipe(dataset, fn=filter_fn)
|
||||
|
||||
if shuffle:
|
||||
buffer_conf = conf.get("shuffle_conf", {})
|
||||
buffer_size = buffer_conf["shuffle_size"]
|
||||
sort_size = buffer_conf["sort_size"]
|
||||
else:
|
||||
buffer_size = 0
|
||||
sort_size = 1
|
||||
|
||||
batch_conf = conf.get("batch_conf", {})
|
||||
batch_size = batch_conf["batch_size"]
|
||||
batch_type = batch_conf["batch_type"]
|
||||
|
||||
assert batch_type in ["example", "token"]
|
||||
if batch_type == "example":
|
||||
len_fn = len_fn_example
|
||||
else:
|
||||
len_fn = len_fn_token
|
||||
|
||||
dataset = MaxTokenBucketizerIterDataPipe(
|
||||
dataset,
|
||||
batch_size=batch_size,
|
||||
len_fn=len_fn,
|
||||
buffer_size=buffer_size,
|
||||
sort_size=sort_size,
|
||||
batch_mode=batch_mode,
|
||||
)
|
||||
|
||||
int_pad_value = conf.get("int_pad_value", -1)
|
||||
float_pad_value = conf.get("float_pad_value", 0.0)
|
||||
padding_conf = {"int_pad_value": int_pad_value, "float_pad_value": float_pad_value}
|
||||
padding_fn = partial(padding, **padding_conf)
|
||||
dataset = MapperIterDataPipe(dataset, fn=padding_fn if batch_mode == "padding" else clipping)
|
||||
|
||||
return dataset
|
||||
Whitespace-only changes.
@@ -0,0 +1,44 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from funasr.datasets.large_datasets.collate_fn import crop_to_max_size
|
||||
|
||||
|
||||
def clipping(data):
|
||||
assert isinstance(data, list)
|
||||
assert "key" in data[0]
|
||||
|
||||
keys = [x["key"] for x in data]
|
||||
|
||||
batch = {}
|
||||
data_names = data[0].keys()
|
||||
for data_name in data_names:
|
||||
if data_name == "key":
|
||||
continue
|
||||
else:
|
||||
if data[0][data_name].dtype.kind == "i":
|
||||
tensor_type = torch.int64
|
||||
else:
|
||||
tensor_type = torch.float32
|
||||
|
||||
tensor_list = [torch.tensor(np.copy(d[data_name]), dtype=tensor_type) for d in data]
|
||||
tensor_lengths = torch.tensor([len(d[data_name]) for d in data], dtype=torch.int32)
|
||||
|
||||
length_clip = min(tensor_lengths)
|
||||
tensor_clip = tensor_list[0].new_zeros(
|
||||
len(tensor_list), length_clip, tensor_list[0].shape[1]
|
||||
)
|
||||
for i, (tensor, length) in enumerate(zip(tensor_list, tensor_lengths)):
|
||||
diff = length - length_clip
|
||||
assert diff >= 0
|
||||
if diff == 0:
|
||||
tensor_clip[i] = tensor
|
||||
else:
|
||||
tensor_clip[i] = crop_to_max_size(tensor, length_clip)
|
||||
|
||||
batch[data_name] = tensor_clip
|
||||
batch[data_name + "_lengths"] = torch.tensor(
|
||||
[tensor.shape[0] for tensor in tensor_clip], dtype=torch.long
|
||||
)
|
||||
|
||||
return keys, batch
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
|
||||
def filter(
|
||||
data, speech_length_min=100, speech_length_max=15000, token_length_min=0, token_length_max=200
|
||||
):
|
||||
assert "speech" in data or "text" in data
|
||||
|
||||
if "speech" in data and "text" in data:
|
||||
if "sampling_rate" in data:
|
||||
speech_length = (data["speech"].shape[0] / data["sampling_rate"]) * 1000.0
|
||||
else:
|
||||
speech_length = data["speech"].shape[0]
|
||||
num_tokens = len(data["text"])
|
||||
return (
|
||||
speech_length_min < speech_length < speech_length_max
|
||||
and token_length_min < num_tokens < token_length_max
|
||||
)
|
||||
elif "speech" in data:
|
||||
if "sampling_rate" in data:
|
||||
speech_length = (data["speech"].shape[0] / data["sampling_rate"]) * 1000.0
|
||||
else:
|
||||
speech_length = data["speech"].shape[0]
|
||||
return speech_length_min < speech_length < speech_length_max
|
||||
else:
|
||||
num_tokens = len(data["text"])
|
||||
return token_length_min < num_tokens < token_length_max
|
||||
@@ -0,0 +1,42 @@
|
||||
import random
|
||||
|
||||
|
||||
def sample_hotword(
|
||||
length,
|
||||
hotword_min_length,
|
||||
hotword_max_length,
|
||||
sample_rate,
|
||||
double_rate,
|
||||
pre_prob,
|
||||
pre_index=None,
|
||||
pre_hwlist=None,
|
||||
):
|
||||
if length < hotword_min_length:
|
||||
return [-1]
|
||||
if random.random() < sample_rate:
|
||||
if pre_prob > 0 and random.random() < pre_prob and pre_index is not None:
|
||||
return pre_index
|
||||
if length == hotword_min_length:
|
||||
return [0, length - 1]
|
||||
elif random.random() < double_rate and length > hotword_max_length + hotword_min_length + 2:
|
||||
# sample two hotwords in a sentence
|
||||
_max_hw_length = min(hotword_max_length, length // 2)
|
||||
# first hotword
|
||||
start1 = random.randint(0, length // 3)
|
||||
end1 = random.randint(start1 + hotword_min_length - 1, start1 + _max_hw_length - 1)
|
||||
# second hotword
|
||||
start2 = random.randint(end1 + 1, length - hotword_min_length)
|
||||
end2 = random.randint(
|
||||
min(length - 1, start2 + hotword_min_length - 1),
|
||||
min(length - 1, start2 + hotword_max_length - 1),
|
||||
)
|
||||
return [start1, end1, start2, end2]
|
||||
else: # single hotword
|
||||
start = random.randint(0, length - hotword_min_length)
|
||||
end = random.randint(
|
||||
min(length - 1, start + hotword_min_length - 1),
|
||||
min(length - 1, start + hotword_max_length - 1),
|
||||
)
|
||||
return [start, end]
|
||||
else:
|
||||
return [-1]
|
||||
@@ -0,0 +1,30 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def build_LFR_features(data, m, n):
|
||||
"""
|
||||
Actually, this implements stacking frames and skipping frames.
|
||||
if m = 1 and n = 1, just return the origin features.
|
||||
if m = 1 and n > 1, it works like skipping.
|
||||
if m > 1 and n = 1, it works like stacking but only support right frames.
|
||||
if m > 1 and n > 1, it works like LFR.
|
||||
|
||||
Args:
|
||||
inputs_batch: inputs is T x D np.ndarray
|
||||
m: number of frames to stack
|
||||
n: number of frames to skip
|
||||
"""
|
||||
|
||||
LFR_inputs = []
|
||||
T = data.shape[0]
|
||||
T_lfr = int(np.ceil(T / n))
|
||||
for i in range(T_lfr):
|
||||
if m <= T - i * n:
|
||||
LFR_inputs.append(np.hstack(data[i * n : i * n + m]))
|
||||
else:
|
||||
num_padding = m - (T - i * n)
|
||||
frame = np.hstack(data[i * n :])
|
||||
for _ in range(num_padding):
|
||||
frame = np.hstack((frame, data[-1]))
|
||||
LFR_inputs.append(frame)
|
||||
return np.vstack(LFR_inputs)
|
||||
@@ -0,0 +1,72 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch.nn.utils.rnn import pad_sequence
|
||||
|
||||
|
||||
def padding(data, float_pad_value=0.0, int_pad_value=-1):
|
||||
assert isinstance(data, list)
|
||||
assert "key" in data[0]
|
||||
assert "speech" in data[0] or "text" in data[0]
|
||||
|
||||
keys = [x["key"] for x in data]
|
||||
|
||||
batch = {}
|
||||
data_names = data[0].keys()
|
||||
for data_name in data_names:
|
||||
if data_name == "key" or data_name == "sampling_rate":
|
||||
continue
|
||||
else:
|
||||
if data_name != "hotword_indxs":
|
||||
if data[0][data_name].dtype.kind == "i":
|
||||
pad_value = int_pad_value
|
||||
tensor_type = torch.int64
|
||||
else:
|
||||
pad_value = float_pad_value
|
||||
tensor_type = torch.float32
|
||||
|
||||
tensor_list = [torch.tensor(np.copy(d[data_name]), dtype=tensor_type) for d in data]
|
||||
tensor_lengths = torch.tensor([len(d[data_name]) for d in data], dtype=torch.int32)
|
||||
tensor_pad = pad_sequence(tensor_list, batch_first=True, padding_value=pad_value)
|
||||
batch[data_name] = tensor_pad
|
||||
batch[data_name + "_lengths"] = tensor_lengths
|
||||
|
||||
# SAC LABEL INCLUDE
|
||||
if "hotword_indxs" in batch:
|
||||
# if hotword indxs in batch
|
||||
# use it to slice hotwords out
|
||||
hotword_list = []
|
||||
hotword_lengths = []
|
||||
text = batch["text"]
|
||||
text_lengths = batch["text_lengths"]
|
||||
hotword_indxs = batch["hotword_indxs"]
|
||||
dha_pad = torch.ones_like(text) * -1
|
||||
_, t1 = text.shape
|
||||
t1 += 1 # TODO: as parameter which is same as predictor_bias
|
||||
nth_hw = 0
|
||||
for b, (hotword_indx, one_text, length) in enumerate(
|
||||
zip(hotword_indxs, text, text_lengths)
|
||||
):
|
||||
dha_pad[b][:length] = 8405
|
||||
if hotword_indx[0] != -1:
|
||||
start, end = int(hotword_indx[0]), int(hotword_indx[1])
|
||||
hotword = one_text[start : end + 1]
|
||||
hotword_list.append(hotword)
|
||||
hotword_lengths.append(end - start + 1)
|
||||
dha_pad[b][start : end + 1] = one_text[start : end + 1]
|
||||
nth_hw += 1
|
||||
if len(hotword_indx) == 4 and hotword_indx[2] != -1:
|
||||
# the second hotword if exist
|
||||
start, end = int(hotword_indx[2]), int(hotword_indx[3])
|
||||
hotword_list.append(one_text[start : end + 1])
|
||||
hotword_lengths.append(end - start + 1)
|
||||
dha_pad[b][start : end + 1] = one_text[start : end + 1]
|
||||
nth_hw += 1
|
||||
hotword_list.append(torch.tensor([1]))
|
||||
hotword_lengths.append(1)
|
||||
hotword_pad = pad_sequence(hotword_list, batch_first=True, padding_value=0)
|
||||
batch["hotword_pad"] = hotword_pad
|
||||
batch["hotword_lengths"] = torch.tensor(hotword_lengths, dtype=torch.int32)
|
||||
batch["dha_pad"] = dha_pad
|
||||
del batch["hotword_indxs"]
|
||||
del batch["hotword_indxs_lengths"]
|
||||
return keys, batch
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python
|
||||
import re
|
||||
import numpy as np
|
||||
from funasr.datasets.large_datasets.utils.hotword_utils import sample_hotword
|
||||
|
||||
|
||||
def forward_segment(text, seg_dict):
|
||||
word_list = []
|
||||
i = 0
|
||||
while i < len(text):
|
||||
longest_word = text[i]
|
||||
for j in range(i + 1, len(text) + 1):
|
||||
word = text[i:j]
|
||||
if word in seg_dict:
|
||||
if len(word) > len(longest_word):
|
||||
longest_word = word
|
||||
word_list.append(longest_word)
|
||||
i += len(longest_word)
|
||||
return word_list
|
||||
|
||||
|
||||
def seg_tokenize(txt, seg_dict):
|
||||
pattern = re.compile(r"^[\u4E00-\u9FA50-9]+$")
|
||||
out_txt = ""
|
||||
for word in txt:
|
||||
word = word.lower()
|
||||
if word in seg_dict:
|
||||
out_txt += seg_dict[word] + " "
|
||||
else:
|
||||
if pattern.match(word):
|
||||
for char in word:
|
||||
if char in seg_dict:
|
||||
out_txt += seg_dict[char] + " "
|
||||
else:
|
||||
out_txt += "<unk>" + " "
|
||||
else:
|
||||
out_txt += "<unk>" + " "
|
||||
return out_txt.strip().split()
|
||||
|
||||
|
||||
def tokenize(data, vocab=None, seg_dict=None, punc_dict=None, bpe_tokenizer=None, hw_config=None):
|
||||
assert "text" in data
|
||||
assert isinstance(vocab, dict)
|
||||
text = data["text"]
|
||||
token = []
|
||||
vad = -2
|
||||
if bpe_tokenizer is not None:
|
||||
text = bpe_tokenizer.text2tokens(" ".join(text))
|
||||
if seg_dict is not None:
|
||||
assert isinstance(seg_dict, dict)
|
||||
text = seg_tokenize(text, seg_dict)
|
||||
|
||||
length = len(text)
|
||||
if "hw_tag" in data:
|
||||
pre_index = None
|
||||
if hw_config["pre_hwlist"] is not None and hw_config["pre_prob"] > 0:
|
||||
# enable preset hotword detect in sampling
|
||||
for hw in hw_config["pre_hwlist"]:
|
||||
hw = " ".join(seg_tokenize(hw, seg_dict))
|
||||
_find = " ".join(text).find(hw)
|
||||
if _find != -1:
|
||||
# _find = text[:_find].count(" ") # bpe sometimes
|
||||
pre_index = [_find, _find + max(hw.count(" "), 1)]
|
||||
break
|
||||
hotword_indxs = sample_hotword(length, **hw_config, pre_index=pre_index)
|
||||
data["hotword_indxs"] = hotword_indxs
|
||||
del data["hw_tag"]
|
||||
for i in range(length):
|
||||
x = text[i]
|
||||
if i == length - 1 and "punc" in data and x.startswith("vad:"):
|
||||
vad = x[4:]
|
||||
if len(vad) == 0:
|
||||
vad = -1
|
||||
else:
|
||||
vad = int(vad)
|
||||
elif x in vocab:
|
||||
token.append(vocab[x])
|
||||
else:
|
||||
token.append(vocab["<unk>"])
|
||||
|
||||
if "punc" in data and punc_dict is not None:
|
||||
punc_token = []
|
||||
for punc in data["punc"]:
|
||||
if punc in punc_dict:
|
||||
punc_token.append(punc_dict[punc])
|
||||
else:
|
||||
punc_token.append(punc_dict["_"])
|
||||
data["punc"] = np.array(punc_token)
|
||||
|
||||
data["text"] = np.array(token)
|
||||
if vad is not -2:
|
||||
data["vad_indexes"] = np.array([vad], dtype=np.int64)
|
||||
return data
|
||||
Whitespace-only changes.
@@ -0,0 +1,436 @@
|
||||
import torch
|
||||
import copy
|
||||
|
||||
from funasr.register import tables
|
||||
from funasr.utils.load_utils import extract_fbank, load_audio_text_image_video
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioLLMNARDataset")
|
||||
class AudioLLMNARDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
AudioLLMDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf", {})
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf", {}))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.float_pad_value = float_pad_value
|
||||
self.prompt = kwargs.get("prompt", "Please copy the following text.")
|
||||
self.prompt_pre = "USER: \nINSTRUCTION: {}\nINPUT: ".format(
|
||||
self.prompt
|
||||
) # "USER: \nINSTRUCTION: {}\nINPUT: {}\nASSISTANT: "
|
||||
self.prompt_af = ""
|
||||
self.IGNORE_INDEX = kwargs.get("IGNORE_INDEX", -100)
|
||||
self.int_pad_value = self.IGNORE_INDEX
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
speech = speech.squeeze(0)
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
|
||||
prompt_ids_pre = self.tokenizer.encode(self.prompt_pre) # [bos,prompt]
|
||||
prompt_ids_length = len(prompt_ids_pre)
|
||||
|
||||
# bos prompt audio bos target
|
||||
# prompt_input = "{}{}".format(self.prompt_pre, target)
|
||||
# prompt_input_ids = self.tokenizer.encode(prompt_input) #[bos, prompt, input]
|
||||
# audio_length = len(prompt_input_ids) - prompt_ids_length
|
||||
target_ids = self.tokenizer.encode(target)
|
||||
if target_ids[0] == self.tokenizer.bos_token_id:
|
||||
target_ids = target_ids[1:]
|
||||
target_ids_length = len(target_ids)
|
||||
audio_length = target_ids_length
|
||||
input_ids = (
|
||||
prompt_ids_pre + target_ids + [self.tokenizer.pad_token_id] + target_ids
|
||||
) # [bos, prompt, input, pad, target]
|
||||
input_ids = torch.tensor(
|
||||
copy.deepcopy(input_ids), dtype=torch.int64
|
||||
) # [bos, prompt, input, pad, target]
|
||||
input_ids[prompt_ids_length : prompt_ids_length + audio_length] = (
|
||||
-1
|
||||
) # [bos, prompt,-1, pad, target] # it is no need, only for check
|
||||
attention_mask = input_ids.ge(-1) # [true, true, true, true, true], length mask
|
||||
|
||||
# bos prompt audio target eos
|
||||
# prompt_answer = "{}{}".format(self.prompt_pre, target)
|
||||
# prompt_answer_ids = self.tokenizer.encode(prompt_answer) #[bos, prompt, input]
|
||||
# answer_length = len(prompt_answer_ids) - prompt_ids_length
|
||||
target_ids = self.tokenizer.encode(target)
|
||||
if target_ids[0] == self.tokenizer.bos_token_id:
|
||||
target_ids = target_ids[1:]
|
||||
# target_ids_length = len(target_ids)
|
||||
labels_ids = (
|
||||
prompt_ids_pre + target_ids + target_ids + [self.tokenizer.eos_token_id]
|
||||
) # [bos, prompt, input, target, eos]
|
||||
labels_ids = torch.tensor(
|
||||
copy.deepcopy(labels_ids), dtype=torch.int64
|
||||
) # [bos, prompt, input, target, eos]
|
||||
labels_ids[:prompt_ids_length] = -1 # [-1, -1, input, target, eos]
|
||||
label_mask = labels_ids.ge(0) # [false, false, true, true, true], length mask
|
||||
labels_ids[~label_mask] = self.IGNORE_INDEX # [-1, -1, input, target, eos]
|
||||
|
||||
audio_mask = (
|
||||
[0] * prompt_ids_length + [1] * audio_length + [0] * target_ids_length + [0]
|
||||
) # [0, 0, 1, 0, 0]
|
||||
audio_mask = torch.tensor(audio_mask, dtype=torch.float32)
|
||||
|
||||
ids = target_ids # self.tokenizer.encode(target) # token ids is different from labels_ids
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
text_lengths = torch.tensor([len(ids)], dtype=torch.int32)
|
||||
|
||||
prompt_bos_length = torch.tensor([len(prompt_ids_pre)], dtype=torch.int32)
|
||||
|
||||
return {
|
||||
"speech": speech,
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"labels_ids": labels_ids,
|
||||
"label_mask": label_mask,
|
||||
"audio_mask": audio_mask,
|
||||
"prompt_bos_length": prompt_bos_length,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
return outputs
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioLLMDataset")
|
||||
class AudioLLMDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
AudioLLMDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf", {})
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf", {}))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.float_pad_value = float_pad_value
|
||||
self.prompt = kwargs.get("prompt", "Transcribe speech to text.")
|
||||
self.prompt_pre = "USER: \nINSTRUCTION: {}\nINPUT: ".format(
|
||||
self.prompt
|
||||
) # "USER: \nINSTRUCTION: {}\nnINPUT: {}\nASSISTANT: "
|
||||
self.prompt_af = ""
|
||||
self.IGNORE_INDEX = kwargs.get("IGNORE_INDEX", -100)
|
||||
self.int_pad_value = self.IGNORE_INDEX
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
speech = speech.squeeze(0)
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
|
||||
prompt_ids_pre = self.tokenizer.encode(self.prompt_pre) # [bos,prompt]
|
||||
prompt_ids_length = len(prompt_ids_pre)
|
||||
|
||||
prompt_input = "{}{}".format(self.prompt_pre, target)
|
||||
prompt_input_ids = self.tokenizer.encode(prompt_input)
|
||||
audio_length = len(prompt_input_ids) - prompt_ids_length
|
||||
input_ids = prompt_input_ids + [self.tokenizer.pad_token_id]
|
||||
input_ids = torch.tensor(input_ids, dtype=torch.int64) # [bos, prompt, input, pad]
|
||||
input_ids[prompt_ids_length:] = -1 # [bos, prompt,-1,-1]
|
||||
attention_mask = input_ids.ge(-1) # [true, true, true, true], length mask
|
||||
|
||||
prompt_answer = "{}{}".format(self.prompt_pre, target)
|
||||
prompt_answer_ids = self.tokenizer.encode(prompt_answer)
|
||||
answer_length = len(prompt_answer_ids) - prompt_ids_length
|
||||
labels_ids = copy.deepcopy(prompt_input_ids) + [self.tokenizer.eos_token_id]
|
||||
labels_ids = torch.tensor(labels_ids, dtype=torch.int64) # [bos, prompt, input, eos]
|
||||
labels_ids[:prompt_ids_length] = -1 # [-1, -1, input, eos]
|
||||
label_mask = labels_ids.ge(0) # [False,False,True,True]
|
||||
labels_ids[~label_mask] = self.IGNORE_INDEX # [-100,-100,input,eos]
|
||||
|
||||
audio_mask = [0] * prompt_ids_length + [1] * audio_length + [0]
|
||||
audio_mask = torch.tensor(audio_mask, dtype=torch.float32)
|
||||
|
||||
ids = self.tokenizer.encode(target) # token ids is different from labels_ids
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
text_lengths = torch.tensor([len(ids)], dtype=torch.int32)
|
||||
|
||||
return {
|
||||
"speech": speech,
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"labels_ids": labels_ids,
|
||||
"label_mask": label_mask,
|
||||
"audio_mask": audio_mask,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
return outputs
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioLLMARDataset")
|
||||
class AudioLLMARDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
AudioLLMDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf", {})
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf", {}))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.float_pad_value = float_pad_value
|
||||
self.prompt = kwargs.get("prompt", "Transcribe speech to text.")
|
||||
self.prompt_pre = "USER: \nINSTRUCTION: {}\nINPUT: ".format(
|
||||
self.prompt
|
||||
) # "USER: \nINSTRUCTION: {}\nnINPUT: {}\nASSISTANT: "
|
||||
self.prompt_af = ""
|
||||
self.IGNORE_INDEX = kwargs.get("IGNORE_INDEX", -100)
|
||||
self.int_pad_value = self.IGNORE_INDEX
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
speech = speech.squeeze(0)
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
|
||||
prompt_ids_pre = self.tokenizer.encode(self.prompt_pre) # [bos,prompt]
|
||||
prompt_ids_length = len(prompt_ids_pre)
|
||||
|
||||
prompt_input = "{}{}".format(self.prompt_pre, target)
|
||||
prompt_input_ids = self.tokenizer.encode(prompt_input)
|
||||
audio_length = len(prompt_input_ids) - prompt_ids_length
|
||||
input_ids = prompt_input_ids + [self.tokenizer.pad_token_id]
|
||||
input_ids = torch.tensor(input_ids, dtype=torch.int64) # [bos, prompt, input, pad]
|
||||
input_ids[prompt_ids_length:] = -1 # [bos, prompt,-1,-1]
|
||||
attention_mask = input_ids.ge(-1) # [true, true, true, true], length mask
|
||||
|
||||
prompt_answer = "{}{}".format(self.prompt_pre, target)
|
||||
prompt_answer_ids = self.tokenizer.encode(prompt_answer)
|
||||
answer_length = len(prompt_answer_ids) - prompt_ids_length
|
||||
labels_ids = copy.deepcopy(prompt_input_ids) + [self.tokenizer.eos_token_id]
|
||||
labels_ids = torch.tensor(labels_ids, dtype=torch.int64) # [bos, prompt, input, eos]
|
||||
labels_ids[:prompt_ids_length] = -1 # [-1, -1, input, eos]
|
||||
label_mask = labels_ids.ge(0) # [False,False,True,True]
|
||||
labels_ids[~label_mask] = self.IGNORE_INDEX # [-100,-100,input,eos]
|
||||
|
||||
audio_mask = [0] * prompt_ids_length + [1] * audio_length + [0]
|
||||
audio_mask = torch.tensor(audio_mask, dtype=torch.float32)
|
||||
|
||||
ids = self.tokenizer.encode(target) # token ids is different from labels_ids
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
text_lengths = torch.tensor([len(ids)], dtype=torch.int32)
|
||||
|
||||
return {
|
||||
"speech": speech,
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"labels_ids": labels_ids,
|
||||
"label_mask": label_mask,
|
||||
"audio_mask": audio_mask,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
return outputs
|
||||
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import logging
|
||||
import concurrent.futures
|
||||
import librosa
|
||||
import torch.distributed as dist
|
||||
from typing import Collection
|
||||
import torch
|
||||
import torchaudio
|
||||
from torch import nn
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
from funasr.tokenizer.cleaner import TextCleaner
|
||||
from funasr.register import tables
|
||||
|
||||
|
||||
@tables.register("preprocessor_classes", "TextPreprocessRemovePunctuation")
|
||||
class TextPreprocessRemovePunctuation(nn.Module):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, text, **kwargs):
|
||||
# 定义英文标点符号
|
||||
en_punct = string.punctuation
|
||||
# 定义中文标点符号(部分常用的)
|
||||
cn_punct = "。?!,、;:“”‘’()《》【】…—~·"
|
||||
# 合并英文和中文标点符号
|
||||
all_punct = en_punct + cn_punct
|
||||
# 创建正则表达式模式,匹配任何在all_punct中的字符
|
||||
punct_pattern = re.compile("[{}]".format(re.escape(all_punct)))
|
||||
# 使用正则表达式的sub方法替换掉这些字符
|
||||
return punct_pattern.sub("", text)
|
||||
Whitespace-only changes.
@@ -0,0 +1,161 @@
|
||||
import torch
|
||||
import copy
|
||||
|
||||
from funasr.register import tables
|
||||
from funasr.utils.load_utils import extract_fbank, load_audio_text_image_video
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioLLMQwenAudioDataset")
|
||||
class AudioLLMQwenAudioDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
AudioLLMDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf", {})
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf", {}))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.float_pad_value = float_pad_value
|
||||
self.prompt = kwargs.get("prompt", "Transcribe speech to text.")
|
||||
# self.prompt_pre = "USER: \nINSTRUCTION: {}\nINPUT: ".format(self.prompt) # "USER: \nINSTRUCTION: {}\nnINPUT: {}\nASSISTANT: "
|
||||
self.prompt_af = ""
|
||||
self.IGNORE_INDEX = kwargs.get("IGNORE_INDEX", -100)
|
||||
self.int_pad_value = self.IGNORE_INDEX
|
||||
self.audio_adaptor_downsample_rate = kwargs.get("audio_adaptor_downsample_rate", 5)
|
||||
self.audio_encoder_downsample_rate = kwargs.get("audio_encoder_downsample_rate", 2)
|
||||
self.prompt_template = "{}"
|
||||
self.answer_template = "{}"
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
speech = speech.squeeze(0)
|
||||
|
||||
audio_pseudo_length = (
|
||||
(speech.shape[0] + 1)
|
||||
// self.audio_adaptor_downsample_rate
|
||||
// self.audio_encoder_downsample_rate
|
||||
)
|
||||
audio_pseudo = torch.full((audio_pseudo_length,), -1) # placeholder
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
|
||||
self.prompt_pre = self.prompt_template.format(self.prompt)
|
||||
prompt_ids_pre = self.tokenizer.encode(self.prompt_pre) # [bos,prompt]
|
||||
prompt_pre_length = len(prompt_ids_pre)
|
||||
|
||||
# input
|
||||
input = self.answer_template.format(target.lower())
|
||||
prompt_input = "{}{}".format(self.prompt_pre, input)
|
||||
prompt_input_ids = self.tokenizer.encode(prompt_input) # [bos, prompt, input]
|
||||
# audio_length = len(prompt_input_ids) - prompt_pre_length
|
||||
input_ids = prompt_input_ids + [self.tokenizer.pad_token_id] # [bos, prompt, input, pad]
|
||||
input_ids_length = len(input_ids)
|
||||
input_ids = torch.tensor(input_ids, dtype=torch.int64) # [bos, prompt, input, pad]
|
||||
input_ids = torch.cat((audio_pseudo, input_ids)) # [audio, bos, prompt, input, pad]
|
||||
# input_ids[:audio_pseudo_length] = -1 # [-1, bos, prompt, input, pad]
|
||||
attention_mask = input_ids.ge(-1) # [true, true, true, true, true], length mask
|
||||
# input_ids[prompt_pre_length:] = -1 # [bos, prompt,-1,-1]
|
||||
# attention_mask = input_ids.ge(-1) # [true, true, true, true], length mask
|
||||
|
||||
# label
|
||||
answer = self.answer_template.format(target.lower())
|
||||
prompt_answer = "{}{}".format(self.prompt_pre, answer)
|
||||
prompt_answer_ids = self.tokenizer.encode(prompt_answer)
|
||||
# answer_length = len(prompt_answer_ids) - prompt_pre_length
|
||||
labels_ids = copy.deepcopy(prompt_answer_ids) + [self.tokenizer.eos_token_id]
|
||||
labels_ids = torch.tensor(labels_ids, dtype=torch.int64) # [bos, prompt, answer, eos]
|
||||
labels_ids = torch.cat((audio_pseudo, labels_ids)) # [audio, bos, prompt, answer, eos]
|
||||
labels_ids[: audio_pseudo_length + prompt_pre_length] = -1 # [-1, -1, -1, answer, eos]
|
||||
# labels_ids[:prompt_pre_length] = -1 # [-1, -1, input, eos]
|
||||
label_mask = labels_ids.ge(0) # [false, false, false, true, true]
|
||||
labels_ids[~label_mask] = self.IGNORE_INDEX # [-100, -100, -100, answer, eos]
|
||||
|
||||
# audio_mask for input_ids
|
||||
audio_mask = [1] * audio_pseudo_length + [0] * input_ids_length
|
||||
audio_mask = torch.tensor(audio_mask, dtype=torch.float32)
|
||||
|
||||
ids = self.tokenizer.encode(target) # token ids is different from labels_ids
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
text_lengths = torch.tensor([len(ids)], dtype=torch.int32)
|
||||
|
||||
return {
|
||||
"speech": speech,
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"labels_ids": labels_ids,
|
||||
"label_mask": label_mask,
|
||||
"audio_mask": audio_mask,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
return outputs
|
||||
Whitespace-only changes.
@@ -0,0 +1,161 @@
|
||||
import torch
|
||||
import copy
|
||||
|
||||
from funasr.register import tables
|
||||
from funasr.utils.load_utils import extract_fbank, load_audio_text_image_video
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "AudioLLMVicunaDataset")
|
||||
class AudioLLMVicunaDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
AudioLLMDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf", {})
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf", {}))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.float_pad_value = float_pad_value
|
||||
self.prompt = kwargs.get("prompt", "Transcribe speech to text.")
|
||||
# self.prompt_pre = "USER: \nINSTRUCTION: {}\nINPUT: ".format(self.prompt) # "USER: \nINSTRUCTION: {}\nnINPUT: {}\nASSISTANT: "
|
||||
self.prompt_af = ""
|
||||
self.IGNORE_INDEX = kwargs.get("IGNORE_INDEX", -100)
|
||||
self.int_pad_value = self.IGNORE_INDEX
|
||||
self.audio_adaptor_downsample_rate = kwargs.get("audio_adaptor_downsample_rate", 5)
|
||||
self.audio_encoder_downsample_rate = kwargs.get("audio_encoder_downsample_rate", 2)
|
||||
self.prompt_template = "USER: {}\n ASSISTANT:"
|
||||
self.answer_template = "{}"
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
item = self.index_ds[index]
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
source = item["source"]
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
speech = speech.squeeze(0)
|
||||
|
||||
audio_pseudo_length = (
|
||||
(speech.shape[0] + 1)
|
||||
// self.audio_adaptor_downsample_rate
|
||||
// self.audio_encoder_downsample_rate
|
||||
)
|
||||
audio_pseudo = torch.full((audio_pseudo_length,), -1) # placeholder
|
||||
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
|
||||
self.prompt_pre = self.prompt_template.format(self.prompt)
|
||||
prompt_ids_pre = self.tokenizer.encode(self.prompt_pre) # [bos,prompt]
|
||||
prompt_pre_length = len(prompt_ids_pre)
|
||||
|
||||
# input
|
||||
input = self.answer_template.format(target.lower())
|
||||
prompt_input = "{}{}".format(self.prompt_pre, input)
|
||||
prompt_input_ids = self.tokenizer.encode(prompt_input) # [bos, prompt, input]
|
||||
# audio_length = len(prompt_input_ids) - prompt_pre_length
|
||||
input_ids = prompt_input_ids + [self.tokenizer.pad_token_id] # [bos, prompt, input, pad]
|
||||
input_ids_length = len(input_ids)
|
||||
input_ids = torch.tensor(input_ids, dtype=torch.int64) # [bos, prompt, input, pad]
|
||||
input_ids = torch.cat((audio_pseudo, input_ids)) # [audio, bos, prompt, input, pad]
|
||||
# input_ids[:audio_pseudo_length] = -1 # [-1, bos, prompt, input, pad]
|
||||
attention_mask = input_ids.ge(-1) # [true, true, true, true, true], length mask
|
||||
# input_ids[prompt_pre_length:] = -1 # [bos, prompt,-1,-1]
|
||||
# attention_mask = input_ids.ge(-1) # [true, true, true, true], length mask
|
||||
|
||||
# label
|
||||
answer = self.answer_template.format(target.lower())
|
||||
prompt_answer = "{}{}".format(self.prompt_pre, answer)
|
||||
prompt_answer_ids = self.tokenizer.encode(prompt_answer)
|
||||
# answer_length = len(prompt_answer_ids) - prompt_pre_length
|
||||
labels_ids = copy.deepcopy(prompt_answer_ids) + [self.tokenizer.eos_token_id]
|
||||
labels_ids = torch.tensor(labels_ids, dtype=torch.int64) # [bos, prompt, answer, eos]
|
||||
labels_ids = torch.cat((audio_pseudo, labels_ids)) # [audio, bos, prompt, answer, eos]
|
||||
labels_ids[: audio_pseudo_length + prompt_pre_length] = -1 # [-1, -1, -1, answer, eos]
|
||||
# labels_ids[:prompt_pre_length] = -1 # [-1, -1, input, eos]
|
||||
label_mask = labels_ids.ge(0) # [false, false, false, true, true]
|
||||
labels_ids[~label_mask] = self.IGNORE_INDEX # [-100, -100, -100, answer, eos]
|
||||
|
||||
# audio_mask for input_ids
|
||||
audio_mask = [1] * audio_pseudo_length + [0] * input_ids_length
|
||||
audio_mask = torch.tensor(audio_mask, dtype=torch.float32)
|
||||
|
||||
ids = self.tokenizer.encode(target) # token ids is different from labels_ids
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
text_lengths = torch.tensor([len(ids)], dtype=torch.int32)
|
||||
|
||||
return {
|
||||
"speech": speech,
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"labels_ids": labels_ids,
|
||||
"label_mask": label_mask,
|
||||
"audio_mask": audio_mask,
|
||||
}
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
return outputs
|
||||
Whitespace-only changes.
@@ -0,0 +1,232 @@
|
||||
import logging
|
||||
|
||||
import torch
|
||||
import random
|
||||
import traceback
|
||||
from funasr.register import tables
|
||||
from funasr.utils.load_utils import extract_fbank, load_audio_text_image_video
|
||||
|
||||
|
||||
@tables.register("dataset_classes", "SenseVoiceDataset")
|
||||
class SenseVoiceDataset(torch.utils.data.Dataset):
|
||||
"""
|
||||
SenseVoiceDataset
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
index_ds: str = None,
|
||||
frontend=None,
|
||||
tokenizer=None,
|
||||
int_pad_value: int = -1,
|
||||
float_pad_value: float = 0.0,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__()
|
||||
index_ds_class = tables.index_ds_classes.get(index_ds)
|
||||
self.index_ds = index_ds_class(path, **kwargs)
|
||||
preprocessor_speech = kwargs.get("preprocessor_speech", None)
|
||||
if preprocessor_speech:
|
||||
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
|
||||
preprocessor_speech = preprocessor_speech_class(
|
||||
**kwargs.get("preprocessor_speech_conf")
|
||||
)
|
||||
self.preprocessor_speech = preprocessor_speech
|
||||
preprocessor_text = kwargs.get("preprocessor_text", None)
|
||||
if preprocessor_text:
|
||||
preprocessor_text_class = tables.preprocessor_classes.get(preprocessor_text)
|
||||
preprocessor_text = preprocessor_text_class(**kwargs.get("preprocessor_text_conf"))
|
||||
self.preprocessor_text = preprocessor_text
|
||||
|
||||
self.frontend = frontend
|
||||
self.fs = 16000 if frontend is None else frontend.fs
|
||||
self.data_type = "sound"
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
self.int_pad_value = int_pad_value
|
||||
self.float_pad_value = float_pad_value
|
||||
self.sos = kwargs.get("sos", "<|startoftranscript|>")
|
||||
self.eos = kwargs.get("eos", "<|endoftext|>")
|
||||
self.batch_size = kwargs.get("batch_size")
|
||||
self.batch_type = kwargs.get("batch_type")
|
||||
self.prompt_ids_len = 0
|
||||
self.retry = kwargs.get("retry", 5)
|
||||
|
||||
self.permute = False
|
||||
from funasr.frontends.whisper_frontend import WhisperFrontend
|
||||
|
||||
if isinstance(self.frontend, WhisperFrontend):
|
||||
self.permute = True
|
||||
|
||||
def get_source_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_source_len(item)
|
||||
|
||||
def get_target_len(self, index):
|
||||
item = self.index_ds[index]
|
||||
return self.index_ds.get_target_len(item)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.index_ds)
|
||||
|
||||
def __getitem__(self, index):
|
||||
# import pdb;
|
||||
# pdb.set_trace()
|
||||
|
||||
output = None
|
||||
for idx in range(self.retry):
|
||||
if idx == 0:
|
||||
index_cur = index
|
||||
else:
|
||||
index_cur = torch.randint(0, len(self.index_ds), ()).item()
|
||||
|
||||
item = self.index_ds[index_cur]
|
||||
|
||||
source = item["source"]
|
||||
try:
|
||||
data_src = load_audio_text_image_video(source, fs=self.fs)
|
||||
except Exception as e:
|
||||
logging.error(f"Loading wav failed! {str(e)}, {traceback.format_exc()}")
|
||||
continue
|
||||
|
||||
if self.preprocessor_speech:
|
||||
data_src = self.preprocessor_speech(data_src, fs=self.fs)
|
||||
speech, speech_lengths = extract_fbank(
|
||||
data_src, data_type=self.data_type, frontend=self.frontend, is_final=True
|
||||
) # speech: [b, T, d]
|
||||
|
||||
if speech_lengths > self.batch_size:
|
||||
continue
|
||||
if self.permute:
|
||||
speech = speech.permute(0, 2, 1)
|
||||
target = item["target"]
|
||||
if self.preprocessor_text:
|
||||
target = self.preprocessor_text(target)
|
||||
|
||||
task = item.get("prompt", "<|ASR|>")
|
||||
text_language = item.get("text_language", "<|zh|>")
|
||||
|
||||
if isinstance(self.sos, str):
|
||||
prompt = f"{self.sos}{task}{text_language}"
|
||||
prompt_ids = self.tokenizer.encode(prompt, allowed_special="all")
|
||||
else:
|
||||
prompt = f"{task}{text_language}"
|
||||
prompt_ids = self.tokenizer.encode(prompt, allowed_special="all")
|
||||
prompt_ids = [self.sos] + prompt_ids
|
||||
|
||||
prompt_ids_len = len(prompt_ids) - 1 # [sos, task]
|
||||
self.prompt_ids_len = prompt_ids_len
|
||||
|
||||
target_ids = self.tokenizer.encode(target, allowed_special="all")
|
||||
target_ids_len = len(target_ids) + 1 # [lid, text]
|
||||
if target_ids_len > 200:
|
||||
continue
|
||||
|
||||
if isinstance(self.eos, str):
|
||||
eos = self.tokenizer.encode(self.eos, allowed_special="all") # [eos]
|
||||
else:
|
||||
eos = [self.eos]
|
||||
|
||||
ids = prompt_ids + target_ids + eos # [sos, task, lid, text, eos]
|
||||
ids_lengths = len(ids)
|
||||
|
||||
text = torch.tensor(ids, dtype=torch.int64)
|
||||
text_lengths = torch.tensor([ids_lengths], dtype=torch.int32)
|
||||
|
||||
target_mask = (
|
||||
[0] * (prompt_ids_len) + [1] * (target_ids_len) + [1]
|
||||
) # [sos, task, lid, text, eos]: [0, 0, 1, 1, 1]
|
||||
target_mask_lengths = len(target_mask)
|
||||
target_mask = torch.tensor(target_mask, dtype=torch.float32)
|
||||
target_mask_lengths = torch.tensor([target_mask_lengths], dtype=torch.int32)
|
||||
|
||||
output = {
|
||||
"speech": speech[0, :, :],
|
||||
"speech_lengths": speech_lengths,
|
||||
"text": text,
|
||||
"text_lengths": text_lengths,
|
||||
"target_mask": target_mask,
|
||||
"target_mask_lengths": target_mask_lengths,
|
||||
}
|
||||
break
|
||||
|
||||
return output
|
||||
|
||||
def collator(self, samples: list = None):
|
||||
outputs = {}
|
||||
for sample in samples:
|
||||
if sample is None:
|
||||
continue
|
||||
for key in sample.keys():
|
||||
if key not in outputs:
|
||||
outputs[key] = []
|
||||
outputs[key].append(sample[key])
|
||||
|
||||
if len(outputs) < 1:
|
||||
logging.error(f"ERROR: data is empty!")
|
||||
outputs = {
|
||||
"speech": torch.rand((10, 128), dtype=torch.float32)[None, :, :],
|
||||
"speech_lengths": torch.tensor(
|
||||
[
|
||||
10,
|
||||
],
|
||||
dtype=torch.int32,
|
||||
)[:, None],
|
||||
"text": torch.tensor(
|
||||
[
|
||||
58836,
|
||||
],
|
||||
dtype=torch.int32,
|
||||
)[None, :],
|
||||
"text_lengths": torch.tensor(
|
||||
[
|
||||
1,
|
||||
],
|
||||
dtype=torch.int32,
|
||||
)[:, None],
|
||||
"target_mask": torch.tensor([[0] * (self.prompt_ids_len) + [1] * (1) + [1]])[
|
||||
None, :
|
||||
],
|
||||
}
|
||||
return outputs
|
||||
|
||||
for key, data_list in outputs.items():
|
||||
if isinstance(data_list[0], torch.Tensor):
|
||||
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
|
||||
|
||||
pad_value = self.int_pad_value
|
||||
else:
|
||||
pad_value = self.float_pad_value
|
||||
|
||||
outputs[key] = torch.nn.utils.rnn.pad_sequence(
|
||||
data_list, batch_first=True, padding_value=pad_value
|
||||
)
|
||||
|
||||
if self.batch_type != "example":
|
||||
for i in range(10):
|
||||
outputs = self._filter_badcase(outputs, i=i)
|
||||
|
||||
return outputs
|
||||
|
||||
def _filter_badcase(self, outputs, i=0):
|
||||
b, t, _ = outputs["speech"].shape
|
||||
|
||||
if b * t > self.batch_size * 1.25:
|
||||
beg = torch.randint(0, 2, ()).item()
|
||||
if b < 2:
|
||||
beg = 0
|
||||
logging.info(
|
||||
f"Warning, b * t: {b * t} > {self.batch_size}, drop half data {i}th, beg:{beg}"
|
||||
)
|
||||
for key, data_list in outputs.items():
|
||||
outputs[key] = outputs[key][beg : beg + b : 2]
|
||||
|
||||
speech_lengths_max = outputs["speech_lengths"].max().item()
|
||||
outputs["speech"] = outputs["speech"][:, :speech_lengths_max, :]
|
||||
text_lengths_max = outputs["text_lengths"].max().item()
|
||||
outputs["text"] = outputs["text"][:, :text_lengths_max]
|
||||
target_mask_lengths_max = outputs["target_mask_lengths"].max().item()
|
||||
outputs["target_mask"] = outputs["target_mask"][:, :target_mask_lengths_max]
|
||||
|
||||
return outputs
|
||||
Reference in new issue
Block a user