From 56860ee673881e211ff2bda6f560cf9bc496575e Mon Sep 17 00:00:00 2001 From: snakers41 Date: Mon, 23 Nov 2020 10:28:37 +0000 Subject: [PATCH] First commit --- .../.github/ISSUE_TEMPLATE/bug_report.md | 52 ++++++ .../.github/ISSUE_TEMPLATE/feature_request.md | 27 +++ .../questions---help---support.md | 12 ++ silero-vad/CODE_OF_CONDUCT.md | 76 +++++++++ silero-vad/README.md | 154 ++++++++++++++++++ silero-vad/files/silero_logo.jpg | Bin 0 -> 23875 bytes silero-vad/hubconf.py | 28 ++++ silero-vad/models.yml | 14 ++ silero-vad/utils.py | 60 +++++++ 9 files changed, 423 insertions(+) create mode 100644 silero-vad/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 silero-vad/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 silero-vad/.github/ISSUE_TEMPLATE/questions---help---support.md create mode 100644 silero-vad/CODE_OF_CONDUCT.md create mode 100644 silero-vad/README.md create mode 100644 silero-vad/files/silero_logo.jpg create mode 100644 silero-vad/hubconf.py create mode 100644 silero-vad/models.yml create mode 100644 silero-vad/utils.py diff --git a/silero-vad/.github/ISSUE_TEMPLATE/bug_report.md b/silero-vad/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..af3202d --- /dev/null +++ b/silero-vad/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,52 @@ +--- +name: Bug report +about: Create a report to help us improve +title: Bug report - [X] +labels: bug +assignees: snakers4 + +--- + +## 🐛 Bug + + + +## To Reproduce + +Steps to reproduce the behavior: + +1. +2. +3. + + + +## Expected behavior + + + +## Environment + +Please copy and paste the output from this +[environment collection script](https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py) +(or fill out the checklist below manually). + +You can get the script and run it with: +``` +wget https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py +# For security purposes, please check the contents of collect_env.py before running it. +python collect_env.py +``` + + - PyTorch Version (e.g., 1.0): + - OS (e.g., Linux): + - How you installed PyTorch (`conda`, `pip`, source): + - Build command you used (if compiling from source): + - Python version: + - CUDA/cuDNN version: + - GPU models and configuration: + - Any other relevant information: + +## Additional context + + diff --git a/silero-vad/.github/ISSUE_TEMPLATE/feature_request.md b/silero-vad/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bfe42a2 --- /dev/null +++ b/silero-vad/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,27 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: Feature request - [X] +labels: enhancement +assignees: snakers4 + +--- + +## 🚀 Feature + + +## Motivation + + + +## Pitch + + + +## Alternatives + + + +## Additional context + + diff --git a/silero-vad/.github/ISSUE_TEMPLATE/questions---help---support.md b/silero-vad/.github/ISSUE_TEMPLATE/questions---help---support.md new file mode 100644 index 0000000..1eed38e --- /dev/null +++ b/silero-vad/.github/ISSUE_TEMPLATE/questions---help---support.md @@ -0,0 +1,12 @@ +--- +name: Questions / Help / Support +about: Ask for help, support or ask a question +title: "❓ Questions / Help / Support" +labels: help wanted +assignees: snakers4 + +--- + +## ❓ Questions and Help + +We have a [wiki](https://github.com/snakers4/silero-models/wiki) available for our users. Please make sure you have checked it out first. diff --git a/silero-vad/CODE_OF_CONDUCT.md b/silero-vad/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c69125e --- /dev/null +++ b/silero-vad/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at aveysov@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/silero-vad/README.md b/silero-vad/README.md new file mode 100644 index 0000000..eac8179 --- /dev/null +++ b/silero-vad/README.md @@ -0,0 +1,154 @@ + [![Mailing list : test](http://img.shields.io/badge/Email-gray.svg?style=for-the-badge&logo=gmail)](mailto:hello@silero.ai) [![Mailing list : test](http://img.shields.io/badge/Telegram-blue.svg?style=for-the-badge&logo=telegram)](https://t.me/joinchat/Bv9tjhpdXTI22OUgpOIIDg) [![License: CC BY-NC 4.0](https://img.shields.io/badge/License-GNU%20AGPL%203.0-lightgrey.svg?style=for-the-badge)](https://github.com/snakers4/silero-models/blob/master/LICENSE) + + [![Open on Torch Hub](https://img.shields.io/badge/Torch-Hub-red?logo=pytorch&style=for-the-badge)](https://pytorch.org/hub/snakers4_silero-models_stt/) [![Open on TF Hub](https://img.shields.io/badge/TF-Hub-yellow?logo=tensorflow&style=for-the-badge)](https://tfhub.dev/silero/collections/silero-stt/1) + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/snakers4/silero-models/blob/master/examples.ipynb) + +![header)](https://user-images.githubusercontent.com/12515440/89997349-b3523080-dc94-11ea-9906-ca2e8bc50535.png) + +- [Silero VAD](#silero-vad) + - [Getting Started](#getting-started) + - [PyTorch](#pytorch) + - [ONNX](#onnx) + - [Metrics](#metrics) + - [Performance Metrics](#performance-metrics) + - [Quality Metrics](#quality-metrics) + - [Contact](#contact) + - [Get in Touch](#get-in-touch) + - [Commercial Inquiries](#commercial-inquiries) + + +# Silero VAD + +Silero VAD: pre-trained enterprise-grade Voice Activity and Number Detector. +Enterprise-grade Speech Products made refreshingly simple (all see our [STT](https://github.com/snakers4/silero-models)). + +Currently, there are hardly any high quality / modern / free / public voice activity detectors except for WebRTC Voice Activity Detector ([link](https://github.com/wiseman/py-webrtcvad)). + +Also in enterprise it is crucial to be able to anonymize large-scale spoken corpora (i.e. remove personal data). Typically personal data is considered to be private / sensitive if it contains (i) a name (ii) some private ID. Name recognition is highly subjective and would depend on location, but Voice Activity and Number detections are quite general tasks. + +**Key advantages:** + +- Modern, portable; +- Small memory footprint (?); +- Trained on huge spoken corpora and noise / sound libraries; +- Slower than WebRTC, but sufficiently fast for IOT / edge / mobile applications; + +**Typical use cases:** + +- Spoken corpora anonymization; +- Voice detection for IOT / edge / mobile use cases; +- Data cleaning and preparation, number and voice detection in general; + + +Key features / differences: + +## Getting Started + +All of the provided models are listed in the [models.yml](https://github.com/snakers4/silero-models/blob/master/models.yml) file. +Any meta-data and newer versions will be added there. + +Currently we provide the following checkpoints: + +| | PyTorch | ONNX | Quantization | Languages | Colab | +|-----------------|--------------------|--------------------|--------------|---------|-------| +| VAD v1 (vad_v1) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | `ru`, `en`, `de`, `es` | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/snakers4/silero-models/blob/master/examples.ipynb) | + + +### PyTorch + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/snakers4/silero-models/blob/master/examples.ipynb) + +[![Open on Torch Hub](https://img.shields.io/badge/Torch-Hub-red?logo=pytorch&style=for-the-badge)](https://pytorch.org/hub/snakers4_silero-models_stt/) + +```python +import torch +import zipfile +import torchaudio +from glob import glob + +device = torch.device('cpu') # gpu also works, but our models are fast enough for CPU +model, decoder, utils = torch.hub.load(repo_or_dir='snakers4/silero-models', + model='silero_stt', + language='en', # also available 'de', 'es' + device=device) +(read_batch, split_into_batches, + read_audio, prepare_model_input) = utils # see function signature for details + +# download a single file, any format compatible with TorchAudio (soundfile backend) +torch.hub.download_url_to_file('https://opus-codec.org/static/examples/samples/speech_orig.wav', + dst ='speech_orig.wav', progress=True) +test_files = glob('speech_orig.wav') +batches = split_into_batches(test_files, batch_size=10) +input = prepare_model_input(read_batch(batches[0]), + device=device) + +output = model(input) +for example in output: + print(decoder(example.cpu())) +``` + +### ONNX + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/snakers4/silero-models/blob/master/examples.ipynb) + +You can run our model everywhere, where you can import the ONNX model or run ONNX runtime. + +```python +import onnx +import torch +import onnxruntime +from omegaconf import OmegaConf + +language = 'en' # also available 'de', 'es' + +# load provided utils +_, decoder, utils = torch.hub.load(repo_or_dir='snakers4/silero-models', model='silero_stt', language=language) +(read_batch, split_into_batches, + read_audio, prepare_model_input) = utils + +# see available models +torch.hub.download_url_to_file('https://raw.githubusercontent.com/snakers4/silero-models/master/models.yml', 'models.yml') +models = OmegaConf.load('models.yml') +available_languages = list(models.stt_models.keys()) +assert language in available_languages + +# load the actual ONNX model +torch.hub.download_url_to_file(models.stt_models.en.latest.onnx, 'model.onnx', progress=True) +onnx_model = onnx.load('model.onnx') +onnx.checker.check_model(onnx_model) +ort_session = onnxruntime.InferenceSession('model.onnx') + +# download a single file, any format compatible with TorchAudio (soundfile backend) +torch.hub.download_url_to_file('https://opus-codec.org/static/examples/samples/speech_orig.wav', dst ='speech_orig.wav', progress=True) +test_files = ['speech_orig.wav'] +batches = split_into_batches(test_files, batch_size=10) +input = prepare_model_input(read_batch(batches[0])) + +# actual onnx inference and decoding +onnx_input = input.detach().cpu().numpy() +ort_inputs = {'input': onnx_input} +ort_outs = ort_session.run(None, ort_inputs) +decoded = decoder(torch.Tensor(ort_outs[0])[0]) +print(decoded) +``` + +## Metrics + +### Performance Metrics + +Speed metrics here. + +### Quality Metrics + +Quality metrics here. + +## Contact + +### Get in Touch + +Try our models, create an [issue](https://github.com/snakers4/silero-models/issues/new), join our [chat](https://t.me/joinchat/Bv9tjhpdXTI22OUgpOIIDg), [email](mailto:hello@silero.ai) us. + +### Commercial Inquiries + +Please see our [wiki](https://github.com/snakers4/silero-models/wiki) and [tiers](https://github.com/snakers4/silero-models/wiki/Licensing-and-Tiers) for relevant information and [email](mailto:hello@silero.ai) us. diff --git a/silero-vad/files/silero_logo.jpg b/silero-vad/files/silero_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ced1942afa6e996154ec82cd82b442fed718683 GIT binary patch literal 23875 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 BB(MMg literal 0 HcmV?d00001 diff --git a/silero-vad/hubconf.py b/silero-vad/hubconf.py new file mode 100644 index 0000000..6f157cd --- /dev/null +++ b/silero-vad/hubconf.py @@ -0,0 +1,28 @@ +dependencies = ['torch', 'omegaconf', 'torchaudio'] +import torch +from omegaconf import OmegaConf +from utils import (init_jit_model, + read_audio, + read_batch, + split_into_batches, + prepare_model_input) + + +def silero_stt(**kwargs): + """Silero Voice Activity and Number Detector Models + Returns a model and a set of utils + Please see https://github.com/snakers4/silero-vad for usage examples + """ + torch.hub.download_url_to_file('https://raw.githubusercontent.com/snakers4/silero-vad/master/models.yml', + 'silero_vad_models.yml', + progress=False) + models = OmegaConf.load('silero_vad_models.yml') + + model = init_jit_model(model_url=models.latest.jit, + **kwargs) + utils = (read_batch, + split_into_batches, + read_audio, + prepare_model_input) + + return model, utils diff --git a/silero-vad/models.yml b/silero-vad/models.yml new file mode 100644 index 0000000..899c16a --- /dev/null +++ b/silero-vad/models.yml @@ -0,0 +1,14 @@ +# Pre-trained Voice Activity Detector and Number Detector +stt_models: + latest: + meta: + name: "vad_v1" + languages: ['ru', 'en', 'de', 'es'] + samples: + en: "" + de: "" + es: "" + ru: "" + jit: "https://silero-models.ams3.cdn.digitaloceanspaces.com/models/vad/vad_v1_jit.model" + jit_q: "https://silero-models.ams3.cdn.digitaloceanspaces.com/models/vad/vad_v1_jit_q.model" + onnx: "https://silero-models.ams3.cdn.digitaloceanspaces.com/models/vad/vad_v1.onnx" diff --git a/silero-vad/utils.py b/silero-vad/utils.py new file mode 100644 index 0000000..23019df --- /dev/null +++ b/silero-vad/utils.py @@ -0,0 +1,60 @@ +import torch +import tempfile +import torchaudio +from typing import List + +torchaudio.set_audio_backend("soundfile") # switch backend + + +def read_batch(audio_paths: List[str]): + return [read_audio(audio_path) + for audio_path + in audio_paths] + + +def split_into_batches(lst: List[str], + batch_size: int = 10): + return [lst[i:i + batch_size] + for i in + range(0, len(lst), batch_size)] + + +def read_audio(path: str, + target_sr: int = 16000): + + assert torchaudio.get_audio_backend() == 'soundfile' + wav, sr = torchaudio.load(path) + + if wav.size(0) > 1: + wav = wav.mean(dim=0, keepdim=True) + + if sr != target_sr: + transform = torchaudio.transforms.Resample(orig_freq=sr, + new_freq=target_sr) + wav = transform(wav) + sr = target_sr + + assert sr == target_sr + return wav.squeeze(0) + + +def prepare_model_input(batch: List[torch.Tensor], + device=torch.device('cpu')): + max_seqlength = max(max([len(_) for _ in batch]), 12800) + inputs = torch.zeros(len(batch), max_seqlength) + for i, wav in enumerate(batch): + inputs[i, :len(wav)].copy_(wav) + inputs = inputs.to(device) + return inputs + + +def init_jit_model(model_url: str, + device: torch.device = torch.device('cpu')): + torch.set_grad_enabled(False) + with tempfile.NamedTemporaryFile('wb', suffix='.model') as f: + torch.hub.download_url_to_file(model_url, + f.name, + progress=True) + model = torch.jit.load(f.name, map_location=device) + model.eval() + return model