mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-17 00:23:14 +00:00
Ok with the model file this time
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.wav
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
@@ -0,0 +1,32 @@
|
||||
# Use ubuntu as the base image from the hub
|
||||
# then have to build this as a image.
|
||||
FROM nvidia/cuda:12.2.0-devel-ubuntu20.04
|
||||
# Optional: Set environment variables
|
||||
ENV MY_ENV_VARIABLE=value
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# Optional: Run commands to set up the image
|
||||
RUN apt-get update && apt-get install -y curl
|
||||
|
||||
# Preconfigure package selections (as an example)
|
||||
RUN echo "package-name package-name/some-option select option-value" | debconf-set-selections && \
|
||||
apt-get install -y software-properties-common
|
||||
RUN apt install software-properties-common -y
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa
|
||||
RUN apt install python3.10 -y
|
||||
RUN apt-get install -y git
|
||||
RUN sudo apt install ffmpeg
|
||||
# Clone the repository
|
||||
|
||||
# RUN pwd
|
||||
RUN pip3 install requirements.txt
|
||||
|
||||
# Navigate to the cloned directory and checkout the specific commit
|
||||
WORKDIR TestingDockerIO
|
||||
COPY VALL-E-X .
|
||||
|
||||
RUN git pull
|
||||
RUN git reset --hard origin/main
|
||||
#RUN git checkout 47c491a29d10c04c6f1efd01bc7b334d11cad4f2
|
||||
|
||||
# # Allow parameters to be passed
|
||||
# ENTRYPOINT ["python3", "main.py"]
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Songting
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,342 @@
|
||||
# VALL-E X: 多语言文本到语音合成与语音克隆 🔊
|
||||
[](https://discord.gg/qCBRmAnTxg)
|
||||
<br>
|
||||
[English](README.md) | 中文
|
||||
<br>
|
||||
微软[VALL-E X](https://arxiv.org/pdf/2303.03926) 零样本语音合成模型的开源实现.<br>
|
||||
**预训练模型现已向公众开放,供研究或应用使用。**
|
||||

|
||||
|
||||
VALL-E X 是一个强大而创新的多语言文本转语音(TTS)模型,最初由微软发布。虽然微软最初在他们的研究论文中提出了该概念,但并未发布任何代码或预训练模型。我们认识到了这项技术的潜力和价值,复现并训练了一个开源可用的VALL-E X模型。我们很乐意与社区分享我们的预训练模型,让每个人都能体验到次世代TTS的威力。 🎧
|
||||
<br>
|
||||
更多细节请查看 [model card](./model-card.md).
|
||||
|
||||
## 📖 目录
|
||||
* [🚀 更新日志](#-更新日志)
|
||||
* [📢 功能特点](#-功能特点)
|
||||
* [💻 本地安装](#-本地安装)
|
||||
* [🎧 在线Demo](#-在线Demo)
|
||||
* [🐍 使用方法](#-Python中的使用方法)
|
||||
* [❓ FAQ](#-faq)
|
||||
* [🧠 TODO](#-todo)
|
||||
|
||||
## 🚀 Updates
|
||||
**2023.09.10**
|
||||
- 支持AR decoder的batch decoding以实现更稳定的生成结果
|
||||
|
||||
**2023.08.30**
|
||||
- 将EnCodec解码器替换成了Vocos解码器,提升了音质。 (感谢[@v0xie](https://github.com/v0xie))
|
||||
|
||||
**2023.08.23**
|
||||
- 加入了长文本生成功能
|
||||
|
||||
**2023.08.20**
|
||||
- 加入了中文版README
|
||||
|
||||
**2023.08.14**
|
||||
- 预训练模型权重已发布,从[这里](https://drive.google.com/file/d/10gdQWvP-K_e1undkvv0p2b7SU6I4Egyl/view?usp=sharing)下载。
|
||||
|
||||
## 💻 本地安装
|
||||
### 使用pip安装,推荐使用Python 3.10,CUDA 11.7 ~ 12.0,PyTorch 2.0+
|
||||
```commandline
|
||||
git clone https://github.com/Plachtaa/VALL-E-X.git
|
||||
cd VALL-E-X
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
> 注意:如果需要制作prompt,需要安装 ffmpeg 并将其所在文件夹加入到环境变量PATH中
|
||||
|
||||
第一次运行程序时,会自动下载相应的模型。如果下载失败并报错,请按照以下步骤手动下载模型。
|
||||
|
||||
(请注意目录和文件夹的大小写)
|
||||
|
||||
1.检查安装目录下是否存在`checkpoints`文件夹,如果没有,在安装目录下手动创建`checkpoints`文件夹(`./checkpoints/`)。
|
||||
|
||||
2.检查`checkpoints`文件夹中是否有`vallex-checkpoint.pt`文件。如果没有,请从[这里](https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt)
|
||||
手动下载`vallex-checkpoint.pt`文件并放到`checkpoints`文件夹里。
|
||||
|
||||
3.检查安装目录下是否存在`whisper`文件夹,如果没有,在安装目录下手动创建`whisper`文件夹(`./whisper/`)。
|
||||
|
||||
4.检查`whisper`文件夹中是否有`medium.pt`文件。如果没有,请从[这里](https://openaipublic.azureedge.net/main/whisper/models/345ae4da62f9b3d59415adc60127b97c714f32e89e936602e85993674d08dcb1/medium.pt)
|
||||
手动下载`medium.pt`文件并放到`whisper`文件夹里。
|
||||
|
||||
## 🎧 在线Demo
|
||||
如果你不想在本地安装,你可以在线体验VALL-E X的功能,点击下面的任意一个链接即可开始体验。
|
||||
<br>
|
||||
[](https://huggingface.co/spaces/Plachta/VALL-E-X)
|
||||
[](https://colab.research.google.com/drive/1yyD_sz531QntLKowMHo-XxorsFBCfKul?usp=sharing)
|
||||
|
||||
|
||||
## 📢 功能特点
|
||||
|
||||
VALL-E X 配备有一系列尖端功能:
|
||||
|
||||
1. **多语言 TTS**: 可使用三种语言 - 英语、中文和日语 - 进行自然、富有表现力的语音合成。
|
||||
|
||||
2. **零样本语音克隆**: 仅需录制任意说话人的短短的 3~10 秒录音,VALL-E X 就能生成个性化、高质量的语音,完美还原他们的声音。
|
||||
|
||||
<details>
|
||||
<summary><h5>查看示例</h5></summary>
|
||||
|
||||
[prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/a7baa51d-a53a-41cc-a03d-6970f25fcca7)
|
||||
|
||||
|
||||
[output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/b895601a-d126-4138-beff-061aabdc7985)
|
||||
|
||||
</details>
|
||||
|
||||
3. **语音情感控制**: VALL-E X 可以合成与给定说话人录音相同情感的语音,为音频增添更多表现力。
|
||||
|
||||
<details>
|
||||
<summary><h5>查看示例</h5></summary>
|
||||
|
||||
https://github.com/Plachtaa/VALL-E-X/assets/112609742/56fa9988-925e-4757-82c5-83ecb0df6266
|
||||
|
||||
|
||||
https://github.com/Plachtaa/VALL-E-X/assets/112609742/699c47a3-d502-4801-8364-bd89bcc0b8f1
|
||||
|
||||
</details>
|
||||
|
||||
4. **零样本跨语言语音合成**: VALL-E X 可以合成与给定说话人母语不同的另一种语言,在不影响口音和流利度的同时,保留该说话人的音色与情感。以下是一个使用日语母语者进行英文与中文合成的样例: 🇯🇵 🗣
|
||||
|
||||
<details>
|
||||
<summary><h5>查看示例</h5></summary>
|
||||
|
||||
[jp-prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/ea6e2ee4-139a-41b4-837e-0bd04dda6e19)
|
||||
|
||||
|
||||
[en-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/db8f9782-923f-425e-ba94-e8c1bd48f207)
|
||||
|
||||
|
||||
[zh-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/15829d79-e448-44d3-8965-fafa7a3f8c28)
|
||||
|
||||
</details>
|
||||
|
||||
5. **口音控制**: VALL-E X 允许您控制所合成音频的口音,比如说中文带英语口音或反之。 🇨🇳 💬
|
||||
|
||||
<details>
|
||||
<summary><h5>查看示例</h5></summary>
|
||||
|
||||
[en-prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/f688d7f6-70ef-46ec-b1cc-355c31e78b3b)
|
||||
|
||||
|
||||
[zh-accent-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/be59c7ca-b45b-44ca-a30d-4d800c950ccc)
|
||||
|
||||
|
||||
[en-accent-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/8b4f4f9b-f299-4ea4-a548-137437b71738)
|
||||
|
||||
</details>
|
||||
|
||||
6. **声学环境保留**: 当给定说话人的录音在不同的声学环境下录制时,VALL-E X 可以保留该声学环境,使合成语音听起来更加自然。
|
||||
|
||||
<details>
|
||||
<summary><h5>查看示例</h5></summary>
|
||||
|
||||
[noise-prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/68986d88-abd0-4d1d-96e4-4f893eb9259e)
|
||||
|
||||
|
||||
[noise-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/96c4c612-4516-4683-8804-501b70938608)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
你可以访问我们的[demo页面](https://plachtaa.github.io/) 来浏览更多示例!
|
||||
|
||||
## 💻 Python中的使用方法
|
||||
|
||||
<details open>
|
||||
<summary><h3>🪑 基本使用</h3></summary>
|
||||
|
||||
```python
|
||||
from utils.generation import SAMPLE_RATE, generate_audio, preload_models
|
||||
from scipy.io.wavfile import write as write_wav
|
||||
from IPython.display import Audio
|
||||
|
||||
# download and load all models
|
||||
preload_models()
|
||||
|
||||
# generate audio from text
|
||||
text_prompt = """
|
||||
Hello, my name is Nose. And uh, and I like hamburger. Hahaha... But I also have other interests such as playing tactic toast.
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt)
|
||||
|
||||
# save audio to disk
|
||||
write_wav("vallex_generation.wav", SAMPLE_RATE, audio_array)
|
||||
|
||||
# play text in notebook
|
||||
Audio(audio_array, rate=SAMPLE_RATE)
|
||||
```
|
||||
|
||||
[hamburger.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/578d7bbe-cda9-483e-898c-29646edc8f2e)
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><h3>🌎 多语言</h3></summary>
|
||||
<br>
|
||||
该VALL-E X实现支持三种语言:英语、中文和日语。您可以通过设置`language`参数来指定语言。默认情况下,该模型将自动检测语言。
|
||||
<br>
|
||||
|
||||
```python
|
||||
|
||||
text_prompt = """
|
||||
チュソクは私のお気に入りの祭りです。 私は数日間休んで、友人や家族との時間を過ごすことができます。
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt)
|
||||
```
|
||||
|
||||
[vallex_japanese.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/ee57a688-3e83-4be5-b0fe-019d16eec51c)
|
||||
|
||||
*注意:即使在一句话中混合多种语言的情况下,VALL-E X也能完美地控制口音,但是您需要手动标记各个句子对应的语言以便于我们的G2P工具识别它们。*
|
||||
```python
|
||||
text_prompt = """
|
||||
[EN]The Thirty Years' War was a devastating conflict that had a profound impact on Europe.[EN]
|
||||
[ZH]这是历史的开始。 如果您想听更多,请继续。[ZH]
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt, language='mix')
|
||||
```
|
||||
|
||||
[vallex_codeswitch.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/d8667abf-bd08-499f-a383-a861d852f98a)
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><h3>📼 预设音色</h3></summary>
|
||||
|
||||
我们提供十几种说话人音色可直接VALL-E X使用! 在[这里](/presets)浏览所有可用音色。
|
||||
|
||||
> VALL-E X 尝试匹配给定预设音色的音调、音高、情感和韵律。该模型还尝试保留音乐、环境噪声等。
|
||||
```python
|
||||
text_prompt = """
|
||||
I am an innocent boy with a smoky voice. It is a great honor for me to speak at the United Nations today.
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt, prompt="dingzhen")
|
||||
```
|
||||
|
||||
[smoky.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/d3f55732-b1cd-420f-87d6-eab60db14dc5)
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><h3>🎙声音克隆</h3></summary>
|
||||
|
||||
VALL-E X 支持声音克隆!你可以使用任何人,角色,甚至是你自己的声音,来制作一个音频提示。在你使用该音频提示时,VALL-E X 将会使用与其相似的声音来合成文本。
|
||||
<br>
|
||||
你需要提供一段3~10秒长的语音,以及该语音对应的文本,来制作音频提示。你也可以将文本留空,让[Whisper](https://github.com/openai/whisper)模型为你生成文本。
|
||||
> VALL-E X 尝试匹配给定音频提示的音调、音高、情感和韵律。该模型还尝试保留音乐、环境噪声等。
|
||||
|
||||
```python
|
||||
from utils.prompt_making import make_prompt
|
||||
|
||||
### Use given transcript
|
||||
make_prompt(name="paimon", audio_prompt_path="paimon_prompt.wav",
|
||||
transcript="Just, what was that? Paimon thought we were gonna get eaten.")
|
||||
|
||||
### Alternatively, use whisper
|
||||
make_prompt(name="paimon", audio_prompt_path="paimon_prompt.wav")
|
||||
```
|
||||
来尝试一下刚刚做好的音频提示吧!
|
||||
```python
|
||||
from utils.generation import SAMPLE_RATE, generate_audio, preload_models
|
||||
from scipy.io.wavfile import write as write_wav
|
||||
|
||||
# download and load all models
|
||||
preload_models()
|
||||
|
||||
text_prompt = """
|
||||
Hey, Traveler, Listen to this, This machine has taken my voice, and now it can talk just like me!
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt, prompt="paimon")
|
||||
|
||||
write_wav("paimon_cloned.wav", SAMPLE_RATE, audio_array)
|
||||
|
||||
```
|
||||
|
||||
[paimon_prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/e7922859-9d12-4e2a-8651-e156e4280311)
|
||||
|
||||
|
||||
[paimon_cloned.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/60d3b7e9-5ead-4024-b499-a897ce5f3d5e)
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details open>
|
||||
<summary><h3>🎢用户界面</h3></summary>
|
||||
|
||||
如果你不擅长代码,我们还为VALL-E X创建了一个用户友好的图形界面。它可以让您轻松地与模型进行交互,使语音克隆和多语言语音合成变得轻而易举。
|
||||
<br>
|
||||
使用以下命令启动用户界面:
|
||||
```commandline
|
||||
python -X utf8 launch-ui.py
|
||||
```
|
||||
</details>
|
||||
|
||||
## 🛠️ 硬件要求及推理速度
|
||||
|
||||
VALL-E X 可以在CPU或GPU上运行 (`pytorch 2.0+`, CUDA 11.7 ~ CUDA 12.0).
|
||||
|
||||
若使用GPU运行,你需要至少6GB的显存。
|
||||
|
||||
## ⚙️ Details
|
||||
|
||||
VALL-E X 与 [Bark](https://github.com/suno-ai/bark), [VALL-E](https://arxiv.org/abs/2301.02111) and [AudioLM](https://arxiv.org/abs/2209.03143)类似, 使用GPT风格的模型以自回归方式预测量化音频token,并由[EnCodec](https://github.com/facebookresearch/encodec)解码.
|
||||
<br>
|
||||
与 [Bark](https://github.com/suno-ai/bark) 相比:
|
||||
- ✔ **轻量**: 3️⃣ ✖ 更小,
|
||||
- ✔ **快速**: 4️⃣ ✖ 更快,
|
||||
- ✔ **中文&日文的更高质量**
|
||||
- ✔ **跨语言合成时没有外国口音**
|
||||
- ✔ **开放且易于操作的声音克隆**
|
||||
- ❌ **支持的语言较少**
|
||||
- ❌ **没有用于合成音乐及特殊音效的token**
|
||||
|
||||
### 支持的语言
|
||||
|
||||
| 语言 | 状态 |
|
||||
|---------| :---: |
|
||||
| 英语 (en) | ✅ |
|
||||
| 日语 (ja) | ✅ |
|
||||
| 中文 (zh) | ✅ |
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
#### 在哪里可以下载checkpoint?
|
||||
* 当您第一次运行程序时,我们使用`wget`将模型下载到`./checkpoints/`目录里。
|
||||
* 如果第一次运行时下载失败,请从[这里](https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt)手动下载模型,并将文件放在`./checkpoints/`里。
|
||||
|
||||
#### 需要多少显存?
|
||||
* 6GB 显存(GPU VRAM) - 几乎所有NVIDIA GPU都满足要求.
|
||||
|
||||
#### 为什么模型无法生成长文本?
|
||||
* 当序列长度增加时,Transformer的计算复杂度呈二次方增长。因此,所有训练音频都保持在22秒以下。请确保音频提示(audio prompt)和生成的音频的总长度小于22秒以确保可接受的性能。
|
||||
|
||||
#### 更多...
|
||||
|
||||
## 🧠 待办事项
|
||||
- [x] 添加中文 README
|
||||
- [x] 长文本生成
|
||||
- [x] 用Vocos解码器替换Encodec解码器
|
||||
- [ ] 微调以实现更好的语音自适应
|
||||
- [ ] 给非python用户的`.bat`脚本
|
||||
- [ ] 更多...
|
||||
|
||||
## 🙏 感谢
|
||||
- [VALL-E X paper](https://arxiv.org/pdf/2303.03926) for the brilliant idea
|
||||
- [lifeiteng's vall-e](https://github.com/lifeiteng/vall-e) for related training code
|
||||
- [bark](https://github.com/suno-ai/bark) for the amazing pioneering work in neuro-codec TTS model
|
||||
|
||||
## ⭐️ 表示出你的支持
|
||||
|
||||
如果您觉得VALL-E X有趣且有用,请在GitHub上给我们一颗星! ⭐️ 它鼓励我们不断改进模型并添加令人兴奋的功能。
|
||||
|
||||
## 📜 License
|
||||
|
||||
VALL-E X 使用 [MIT License](./LICENSE).
|
||||
|
||||
---
|
||||
|
||||
有问题或需要帮助? 可以随便 [open an issue](https://github.com/Plachtaa/VALL-E-X/issues/new) 或加入我们的 [Discord](https://discord.gg/qCBRmAnTxg)
|
||||
|
||||
Happy voice cloning! 🎤
|
||||
@@ -0,0 +1,353 @@
|
||||
# VALL-E X: Multilingual Text-to-Speech Synthesis and Voice Cloning 🔊
|
||||
# This is for inference, will require some work to get it to inference with Vall-E repo later on.
|
||||
[](https://discord.gg/qCBRmAnTxg)
|
||||
<br>
|
||||
English | [中文](README-ZH.md)
|
||||
<br>
|
||||
An open source implementation of Microsoft's [VALL-E X](https://arxiv.org/pdf/2303.03926) zero-shot TTS model.<br>
|
||||
**We release our trained model to the public for research or application usage.**
|
||||
|
||||

|
||||
|
||||
VALL-E X is an amazing multilingual text-to-speech (TTS) model proposed by Microsoft. While Microsoft initially publish in their research paper, they did not release any code or pretrained models. Recognizing the potential and value of this technology, our team took on the challenge to reproduce the results and train our own model. We are glad to share our trained VALL-E X model with the community, allowing everyone to experience the power next-generation TTS! 🎧
|
||||
<br>
|
||||
<br>
|
||||
More details about the model are presented in [model card](./model-card.md).
|
||||
|
||||
## 📖 Quick Index
|
||||
* [🚀 Updates](#-updates)
|
||||
* [📢 Features](#-features)
|
||||
* [💻 Installation](#-installation)
|
||||
* [🎧 Demos](#-demos)
|
||||
* [🐍 Usage](#-usage-in-python)
|
||||
* [❓ FAQ](#-faq)
|
||||
* [🧠 TODO](#-todo)
|
||||
|
||||
## 🚀 Updates
|
||||
**2023.09.10**
|
||||
- Added AR decoder batch decoding for more stable generation result.
|
||||
|
||||
**2023.08.30**
|
||||
- Replaced EnCodec decoder with Vocos decoder, improved audio quality. (Thanks to [@v0xie](https://github.com/v0xie))
|
||||
|
||||
**2023.08.23**
|
||||
- Added long text generation.
|
||||
|
||||
**2023.08.20**
|
||||
- Added [Chinese README](README-ZH.md).
|
||||
|
||||
**2023.08.14**
|
||||
- Pretrained VALL-E X checkpoint is now released. Download it [here](https://drive.google.com/file/d/10gdQWvP-K_e1undkvv0p2b7SU6I4Egyl/view?usp=sharing)
|
||||
|
||||
## 💻 Installation
|
||||
### Install with pip, recommended with Python 3.10, CUDA 11.7 ~ 12.0, PyTorch 2.0+
|
||||
```commandline
|
||||
git clone https://github.com/Plachtaa/VALL-E-X.git
|
||||
cd VALL-E-X
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
> Note: If you want to make prompt, you need to install ffmpeg and add its folder to the environment variable PATH.
|
||||
|
||||
When you run the program for the first time, it will automatically download the corresponding model.
|
||||
|
||||
If the download fails and reports an error, please follow the steps below to manually download the model.
|
||||
|
||||
(Please pay attention to the capitalization of folders)
|
||||
|
||||
1. Check whether there is a `checkpoints` folder in the installation directory.
|
||||
If not, manually create a `checkpoints` folder (`./checkpoints/`) in the installation directory.
|
||||
|
||||
2. Check whether there is a `vallex-checkpoint.pt` file in the `checkpoints` folder.
|
||||
If not, please manually download the `vallex-checkpoint.pt` file from [here](https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt) and put it in the `checkpoints` folder.
|
||||
|
||||
3. Check whether there is a `whisper` folder in the installation directory.
|
||||
If not, manually create a `whisper` folder (`./whisper/`) in the installation directory.
|
||||
|
||||
4. Check whether there is a `medium.pt` file in the `whisper` folder.
|
||||
If not, please manually download the `medium.pt` file from [here](https://openaipublic.azureedge.net/main/whisper/models/345ae4da62f9b3d59415adc60127b97c714f32e89e936602e85993674d08dcb1/medium.pt) and put it in the `whisper` folder.
|
||||
|
||||
## 🎧 Demos
|
||||
Not ready to set up the environment on your local machine just yet? No problem! We've got you covered with our online demos. You can try out VALL-E X directly on Hugging Face or Google Colab, experiencing the model's capabilities hassle-free!
|
||||
<br>
|
||||
[](https://huggingface.co/spaces/Plachta/VALL-E-X)
|
||||
[](https://colab.research.google.com/drive/1yyD_sz531QntLKowMHo-XxorsFBCfKul?usp=sharing)
|
||||
|
||||
|
||||
## 📢 Features
|
||||
|
||||
VALL-E X comes packed with cutting-edge functionalities:
|
||||
|
||||
1. **Multilingual TTS**: Speak in three languages - English, Chinese, and Japanese - with natural and expressive speech synthesis.
|
||||
|
||||
2. **Zero-shot Voice Cloning**: Enroll a short 3~10 seconds recording of an unseen speaker, and watch VALL-E X create personalized, high-quality speech that sounds just like them!
|
||||
|
||||
<details>
|
||||
<summary><h5>see example</h5></summary>
|
||||
|
||||
[prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/a7baa51d-a53a-41cc-a03d-6970f25fcca7)
|
||||
|
||||
|
||||
[output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/b895601a-d126-4138-beff-061aabdc7985)
|
||||
|
||||
</details>
|
||||
|
||||
3. **Speech Emotion Control**: Experience the power of emotions! VALL-E X can synthesize speech with the same emotion as the acoustic prompt provided, adding an extra layer of expressiveness to your audio.
|
||||
|
||||
<details>
|
||||
<summary><h5>see example</h5></summary>
|
||||
|
||||
https://github.com/Plachtaa/VALL-E-X/assets/112609742/56fa9988-925e-4757-82c5-83ecb0df6266
|
||||
|
||||
|
||||
https://github.com/Plachtaa/VALL-E-X/assets/112609742/699c47a3-d502-4801-8364-bd89bcc0b8f1
|
||||
|
||||
</details>
|
||||
|
||||
4. **Zero-shot Cross-Lingual Speech Synthesis**: Take monolingual speakers on a linguistic journey! VALL-E X can produce personalized speech in another language without compromising on fluency or accent. Below is a Japanese speaker talk in Chinese & English. 🇯🇵 🗣
|
||||
|
||||
<details>
|
||||
<summary><h5>see example</h5></summary>
|
||||
|
||||
[jp-prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/ea6e2ee4-139a-41b4-837e-0bd04dda6e19)
|
||||
|
||||
|
||||
[en-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/db8f9782-923f-425e-ba94-e8c1bd48f207)
|
||||
|
||||
|
||||
[zh-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/15829d79-e448-44d3-8965-fafa7a3f8c28)
|
||||
|
||||
</details>
|
||||
|
||||
5. **Accent Control**: Get creative with accents! VALL-E X allows you to experiment with different accents, like speaking Chinese with an English accent or vice versa. 🇨🇳 💬
|
||||
|
||||
<details>
|
||||
<summary><h5>see example</h5></summary>
|
||||
|
||||
[en-prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/f688d7f6-70ef-46ec-b1cc-355c31e78b3b)
|
||||
|
||||
|
||||
[zh-accent-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/be59c7ca-b45b-44ca-a30d-4d800c950ccc)
|
||||
|
||||
|
||||
[en-accent-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/8b4f4f9b-f299-4ea4-a548-137437b71738)
|
||||
|
||||
</details>
|
||||
|
||||
6. **Acoustic Environment Maintenance**: No need for perfectly clean audio prompts! VALL-E X adapts to the acoustic environment of the input, making speech generation feel natural and immersive.
|
||||
|
||||
<details>
|
||||
<summary><h5>see example</h5></summary>
|
||||
|
||||
[noise-prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/68986d88-abd0-4d1d-96e4-4f893eb9259e)
|
||||
|
||||
|
||||
[noise-output.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/96c4c612-4516-4683-8804-501b70938608)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Explore our [demo page](https://plachtaa.github.io/) for a lot more examples!
|
||||
|
||||
## 🐍 Usage in Python
|
||||
|
||||
<details open>
|
||||
<summary><h3>🪑 Basics</h3></summary>
|
||||
|
||||
```python
|
||||
from utils.generation import SAMPLE_RATE, generate_audio, preload_models
|
||||
from scipy.io.wavfile import write as write_wav
|
||||
from IPython.display import Audio
|
||||
|
||||
# download and load all models
|
||||
preload_models()
|
||||
|
||||
# generate audio from text
|
||||
text_prompt = """
|
||||
Hello, my name is Nose. And uh, and I like hamburger. Hahaha... But I also have other interests such as playing tactic toast.
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt)
|
||||
|
||||
# save audio to disk
|
||||
write_wav("vallex_generation.wav", SAMPLE_RATE, audio_array)
|
||||
|
||||
# play text in notebook
|
||||
Audio(audio_array, rate=SAMPLE_RATE)
|
||||
```
|
||||
|
||||
[hamburger.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/578d7bbe-cda9-483e-898c-29646edc8f2e)
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><h3>🌎 Foreign Language</h3></summary>
|
||||
<br>
|
||||
This VALL-E X implementation also supports Chinese and Japanese. All three languages have equally awesome performance!
|
||||
<br>
|
||||
|
||||
```python
|
||||
|
||||
text_prompt = """
|
||||
チュソクは私のお気に入りの祭りです。 私は数日間休んで、友人や家族との時間を過ごすことができます。
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt)
|
||||
```
|
||||
|
||||
[vallex_japanese.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/ee57a688-3e83-4be5-b0fe-019d16eec51c)
|
||||
|
||||
*Note: VALL-E X controls accent perfectly even when synthesizing code-switch text. However, you need to manually denote language of respective sentences (since our g2p tool is rule-base)*
|
||||
```python
|
||||
text_prompt = """
|
||||
[EN]The Thirty Years' War was a devastating conflict that had a profound impact on Europe.[EN]
|
||||
[ZH]这是历史的开始。 如果您想听更多,请继续。[ZH]
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt, language='mix')
|
||||
```
|
||||
|
||||
[vallex_codeswitch.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/d8667abf-bd08-499f-a383-a861d852f98a)
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><h3>📼 Voice Presets</h3></summary>
|
||||
|
||||
VALL-E X provides tens of speaker voices which you can directly used for inference! Browse all voices in the [code](/presets)
|
||||
|
||||
> VALL-E X tries to match the tone, pitch, emotion and prosody of a given preset. The model also attempts to preserve music, ambient noise, etc.
|
||||
|
||||
```python
|
||||
text_prompt = """
|
||||
I am an innocent boy with a smoky voice. It is a great honor for me to speak at the United Nations today.
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt, prompt="dingzhen")
|
||||
```
|
||||
|
||||
[smoky.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/d3f55732-b1cd-420f-87d6-eab60db14dc5)
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><h3>🎙Voice Cloning</h3></summary>
|
||||
|
||||
VALL-E X supports voice cloning! You can make a voice prompt with any person, character or even your own voice, and use it like other voice presets.<br>
|
||||
To make a voice prompt, you need to provide a speech of 3~10 seconds long, as well as the transcript of the speech.
|
||||
You can also leave the transcript blank to let the [Whisper](https://github.com/openai/whisper) model to generate the transcript.
|
||||
> VALL-E X tries to match the tone, pitch, emotion and prosody of a given prompt. The model also attempts to preserve music, ambient noise, etc.
|
||||
|
||||
```python
|
||||
from utils.prompt_making import make_prompt
|
||||
|
||||
### Use given transcript
|
||||
make_prompt(name="paimon", audio_prompt_path="paimon_prompt.wav",
|
||||
transcript="Just, what was that? Paimon thought we were gonna get eaten.")
|
||||
|
||||
### Alternatively, use whisper
|
||||
make_prompt(name="paimon", audio_prompt_path="paimon_prompt.wav")
|
||||
```
|
||||
Now let's try out the prompt we've just made!
|
||||
```python
|
||||
from utils.generation import SAMPLE_RATE, generate_audio, preload_models
|
||||
from scipy.io.wavfile import write as write_wav
|
||||
|
||||
# download and load all models
|
||||
preload_models()
|
||||
|
||||
text_prompt = """
|
||||
Hey, Traveler, Listen to this, This machine has taken my voice, and now it can talk just like me!
|
||||
"""
|
||||
audio_array = generate_audio(text_prompt, prompt="paimon")
|
||||
|
||||
write_wav("paimon_cloned.wav", SAMPLE_RATE, audio_array)
|
||||
|
||||
```
|
||||
|
||||
[paimon_prompt.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/e7922859-9d12-4e2a-8651-e156e4280311)
|
||||
|
||||
|
||||
[paimon_cloned.webm](https://github.com/Plachtaa/VALL-E-X/assets/112609742/60d3b7e9-5ead-4024-b499-a897ce5f3d5e)
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details open>
|
||||
<summary><h3>🎢User Interface</h3></summary>
|
||||
|
||||
Not comfortable with codes? No problem! We've also created a user-friendly graphical interface for VALL-E X. It allows you to interact with the model effortlessly, making voice cloning and multilingual speech synthesis a breeze.
|
||||
<br>
|
||||
You can launch the UI by the following command:
|
||||
```commandline
|
||||
python -X utf8 launch-ui.py
|
||||
```
|
||||
</details>
|
||||
|
||||
## 🛠️ Hardware and Inference Speed
|
||||
|
||||
VALL-E X works well on both CPU and GPU (`pytorch 2.0+`, CUDA 11.7 and CUDA 12.0).
|
||||
|
||||
A GPU VRAM of 6GB is enough for running VALL-E X without offloading.
|
||||
|
||||
## ⚙️ Details
|
||||
|
||||
VALL-E X is similar to [Bark](https://github.com/suno-ai/bark), [VALL-E](https://arxiv.org/abs/2301.02111) and [AudioLM](https://arxiv.org/abs/2209.03143), which generates audio in GPT-style by predicting audio tokens quantized by [EnCodec](https://github.com/facebookresearch/encodec).
|
||||
<br>
|
||||
Comparing to [Bark](https://github.com/suno-ai/bark):
|
||||
- ✔ **Light-weighted**: 3️⃣ ✖ smaller,
|
||||
- ✔ **Efficient**: 4️⃣ ✖ faster,
|
||||
- ✔ **Better quality on Chinese & Japanese**
|
||||
- ✔ **Cross-lingual speech without foreign accent**
|
||||
- ✔ **Easy voice-cloning**
|
||||
- ❌ **Less languages**
|
||||
- ❌ **No special tokens for music / sound effects**
|
||||
|
||||
### Supported Languages
|
||||
|
||||
| Language | Status |
|
||||
| --- | :---: |
|
||||
| English (en) | ✅ |
|
||||
| Japanese (ja) | ✅ |
|
||||
| Chinese, simplified (zh) | ✅ |
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
#### Where can I download the model checkpoint?
|
||||
* We use `wget` to download the model to directory `./checkpoints/` when you run the program for the first time.
|
||||
* If the download fails on the first run, please manually download from [this link](https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt), and put the file under directory `./checkpoints/`.
|
||||
|
||||
#### How much VRAM do I need?
|
||||
* 6GB GPU VRAM - Almost all NVIDIA GPUs satisfy the requirement.
|
||||
|
||||
#### Why the model fails to generate long text?
|
||||
* Transformer's computation complexity increases quadratically while the sequence length increases. Hence, all training
|
||||
are kept under 22 seconds. Please make sure the total length of audio prompt and generated audio is less than 22 seconds
|
||||
to ensure acceptable performance.
|
||||
|
||||
|
||||
#### MORE TO BE ADDED...
|
||||
|
||||
## 🧠 TODO
|
||||
- [x] Add Chinese README
|
||||
- [x] Long text generation
|
||||
- [x] Replace Encodec decoder with Vocos decoder
|
||||
- [ ] Fine-tuning for better voice adaptation
|
||||
- [ ] `.bat` scripts for non-python users
|
||||
- [ ] To be added...
|
||||
|
||||
## 🙏 Appreciation
|
||||
- [VALL-E X paper](https://arxiv.org/pdf/2303.03926) for the brilliant idea
|
||||
- [lifeiteng's vall-e](https://github.com/lifeiteng/vall-e) for related training code
|
||||
- [bark](https://github.com/suno-ai/bark) for the amazing pioneering work in neuro-codec TTS model
|
||||
|
||||
## ⭐️ Show Your Support
|
||||
|
||||
If you find VALL-E X interesting and useful, give us a star on GitHub! ⭐️ It encourages us to keep improving the model and adding exciting features.
|
||||
|
||||
## 📜 License
|
||||
|
||||
VALL-E X is licensed under the [MIT License](./LICENSE).
|
||||
|
||||
---
|
||||
|
||||
Have questions or need assistance? Feel free to [open an issue](https://github.com/Plachtaa/VALL-E-X/issues/new) or join our [Discord](https://discord.gg/qCBRmAnTxg)
|
||||
|
||||
Happy voice cloning! 🎤
|
||||
@@ -0,0 +1,3 @@
|
||||
# from .datamodule import *
|
||||
# from .tokenizer import *
|
||||
from .collation import *
|
||||
@@ -0,0 +1,120 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from utils import SymbolTable
|
||||
|
||||
|
||||
class TextTokenCollater:
|
||||
"""Collate list of text tokens
|
||||
|
||||
Map sentences to integers. Sentences are padded to equal length.
|
||||
Beginning and end-of-sequence symbols can be added.
|
||||
|
||||
Example:
|
||||
>>> token_collater = TextTokenCollater(text_tokens)
|
||||
>>> tokens_batch, tokens_lens = token_collater(text)
|
||||
|
||||
Returns:
|
||||
tokens_batch: IntTensor of shape (B, L)
|
||||
B: batch dimension, number of input sentences
|
||||
L: length of the longest sentence
|
||||
tokens_lens: IntTensor of shape (B,)
|
||||
Length of each sentence after adding <eos> and <bos>
|
||||
but before padding.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text_tokens: List[str],
|
||||
add_eos: bool = True,
|
||||
add_bos: bool = True,
|
||||
pad_symbol: str = "<pad>",
|
||||
bos_symbol: str = "<bos>",
|
||||
eos_symbol: str = "<eos>",
|
||||
):
|
||||
self.pad_symbol = pad_symbol
|
||||
|
||||
self.add_eos = add_eos
|
||||
self.add_bos = add_bos
|
||||
|
||||
self.bos_symbol = bos_symbol
|
||||
self.eos_symbol = eos_symbol
|
||||
|
||||
unique_tokens = (
|
||||
[pad_symbol]
|
||||
+ ([bos_symbol] if add_bos else [])
|
||||
+ ([eos_symbol] if add_eos else [])
|
||||
+ sorted(text_tokens)
|
||||
)
|
||||
|
||||
self.token2idx = {token: idx for idx, token in enumerate(unique_tokens)}
|
||||
self.idx2token = [token for token in unique_tokens]
|
||||
|
||||
def index(
|
||||
self, tokens_list: List[str]
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
seqs, seq_lens = [], []
|
||||
for tokens in tokens_list:
|
||||
assert (
|
||||
all([True if s in self.token2idx else False for s in tokens])
|
||||
is True
|
||||
)
|
||||
seq = (
|
||||
([self.bos_symbol] if self.add_bos else [])
|
||||
+ list(tokens)
|
||||
+ ([self.eos_symbol] if self.add_eos else [])
|
||||
)
|
||||
seqs.append(seq)
|
||||
seq_lens.append(len(seq))
|
||||
|
||||
max_len = max(seq_lens)
|
||||
for k, (seq, seq_len) in enumerate(zip(seqs, seq_lens)):
|
||||
seq.extend([self.pad_symbol] * (max_len - seq_len))
|
||||
|
||||
tokens = torch.from_numpy(
|
||||
np.array(
|
||||
[[self.token2idx[token] for token in seq] for seq in seqs],
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
tokens_lens = torch.IntTensor(seq_lens)
|
||||
|
||||
return tokens, tokens_lens
|
||||
|
||||
def __call__(self, texts: List[str]) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
tokens_seqs = [[p for p in text] for text in texts]
|
||||
max_len = len(max(tokens_seqs, key=len))
|
||||
|
||||
seqs = [
|
||||
([self.bos_symbol] if self.add_bos else [])
|
||||
+ list(seq)
|
||||
+ ([self.eos_symbol] if self.add_eos else [])
|
||||
+ [self.pad_symbol] * (max_len - len(seq))
|
||||
for seq in tokens_seqs
|
||||
]
|
||||
|
||||
tokens_batch = torch.from_numpy(
|
||||
np.array(
|
||||
[seq for seq in seqs],
|
||||
dtype=np.int64,
|
||||
)
|
||||
)
|
||||
|
||||
tokens_lens = torch.IntTensor(
|
||||
[
|
||||
len(seq) + int(self.add_eos) + int(self.add_bos)
|
||||
for seq in tokens_seqs
|
||||
]
|
||||
)
|
||||
|
||||
return tokens_batch, tokens_lens
|
||||
|
||||
|
||||
def get_text_token_collater() -> TextTokenCollater:
|
||||
collater = TextTokenCollater(
|
||||
['0'], add_bos=False, add_eos=False
|
||||
)
|
||||
return collater
|
||||
@@ -0,0 +1,419 @@
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import logging
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import torch
|
||||
# from icefall.utils import str2bool
|
||||
# from lhotse import CutSet, load_manifest_lazy
|
||||
# from lhotse.dataset import (
|
||||
# CutConcatenate,
|
||||
# DynamicBucketingSampler,
|
||||
# PrecomputedFeatures,
|
||||
# SingleCutSampler,
|
||||
# SpecAugment,
|
||||
# )
|
||||
# from lhotse.dataset.input_strategies import OnTheFlyFeatures
|
||||
# from lhotse.utils import fix_random_seed
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from data.collation import get_text_token_collater
|
||||
# from data.dataset import SpeechSynthesisDataset
|
||||
from data.fbank import get_fbank_extractor
|
||||
from data.input_strategies import PromptedPrecomputedFeatures
|
||||
|
||||
# PrecomputedFeatures = PrecomputedFeatures
|
||||
|
||||
|
||||
class _SeedWorkers:
|
||||
def __init__(self, seed: int):
|
||||
self.seed = seed
|
||||
|
||||
def __call__(self, worker_id: int):
|
||||
fix_random_seed(self.seed + worker_id)
|
||||
|
||||
|
||||
def _get_input_strategy(input_strategy, dataset, cuts):
|
||||
if input_strategy == "PromptedPrecomputedFeatures":
|
||||
return PromptedPrecomputedFeatures(dataset, cuts)
|
||||
|
||||
return eval(input_strategy)()
|
||||
|
||||
|
||||
class TtsDataModule:
|
||||
"""
|
||||
DataModule for VALL-E TTS experiments.
|
||||
It assumes there is always one train and valid dataloader.
|
||||
|
||||
It contains all the common data pipeline modules used in TTS
|
||||
experiments, e.g.:
|
||||
- dynamic batch size,
|
||||
- bucketing samplers,
|
||||
- cut concatenation[not used & tested yet],
|
||||
- augmentation[not used & tested yet],
|
||||
- on-the-fly feature extraction[not used & tested yet]
|
||||
|
||||
This class should be derived for specific corpora used in TTS tasks.
|
||||
"""
|
||||
|
||||
def __init__(self, args: argparse.Namespace):
|
||||
self.args = args
|
||||
|
||||
@classmethod
|
||||
def add_arguments(cls, parser: argparse.ArgumentParser):
|
||||
group = parser.add_argument_group(
|
||||
title="TTS data related options",
|
||||
description="These options are used for the preparation of "
|
||||
"PyTorch DataLoaders from Lhotse CutSet's -- they control the "
|
||||
"effective batch sizes, sampling strategies, applied data "
|
||||
"augmentations, etc.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--manifest-dir",
|
||||
type=Path,
|
||||
default=Path("data/tokenized"),
|
||||
help="Path to directory with train/valid/test cuts.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--max-duration",
|
||||
type=int,
|
||||
default=40.0,
|
||||
help="Maximum pooled recordings duration (seconds) in a "
|
||||
"single batch. You can reduce it if it causes CUDA OOM.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--bucketing-sampler",
|
||||
type=str2bool,
|
||||
default=True,
|
||||
help="When enabled, the batches will come from buckets of "
|
||||
"similar duration (saves padding frames).",
|
||||
)
|
||||
group.add_argument(
|
||||
"--num-buckets",
|
||||
type=int,
|
||||
default=10,
|
||||
help="The number of buckets for the DynamicBucketingSampler"
|
||||
"(you might want to increase it for larger datasets).",
|
||||
)
|
||||
group.add_argument(
|
||||
"--concatenate-cuts",
|
||||
type=str2bool,
|
||||
default=False,
|
||||
help="When enabled, utterances (cuts) will be concatenated "
|
||||
"to minimize the amount of padding.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--duration-factor",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Determines the maximum duration of a concatenated cut "
|
||||
"relative to the duration of the longest cut in a batch.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--gap",
|
||||
type=float,
|
||||
default=0.1,
|
||||
help="The amount of padding (in seconds) inserted between "
|
||||
"concatenated cuts. This padding is filled with noise when "
|
||||
"noise augmentation is used.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--on-the-fly-feats",
|
||||
type=str2bool,
|
||||
default=False,
|
||||
help="When enabled, use on-the-fly cut mixing and feature "
|
||||
"extraction. Will drop existing precomputed feature manifests "
|
||||
"if available.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--shuffle",
|
||||
type=str2bool,
|
||||
default=True,
|
||||
help="When enabled (=default), the examples will be "
|
||||
"shuffled for each epoch.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--drop-last",
|
||||
type=str2bool,
|
||||
default=False,
|
||||
help="Whether to drop last batch. Used by sampler.",
|
||||
)
|
||||
group.add_argument(
|
||||
"--return-cuts",
|
||||
type=str2bool,
|
||||
default=True,
|
||||
help="When enabled, each batch will have the "
|
||||
"field: batch['supervisions']['cut'] with the cuts that "
|
||||
"were used to construct it.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--num-workers",
|
||||
type=int,
|
||||
default=8,
|
||||
help="The number of training dataloader workers that "
|
||||
"collect the batches.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--enable-spec-aug",
|
||||
type=str2bool,
|
||||
default=False,
|
||||
help="When enabled, use SpecAugment for training dataset.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--spec-aug-time-warp-factor",
|
||||
type=int,
|
||||
default=80,
|
||||
help="Used only when --enable-spec-aug is True. "
|
||||
"It specifies the factor for time warping in SpecAugment. "
|
||||
"Larger values mean more warping. "
|
||||
"A value less than 1 means to disable time warp.",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--input-strategy",
|
||||
type=str,
|
||||
default="PrecomputedFeatures",
|
||||
help="AudioSamples or PrecomputedFeatures or PromptedPrecomputedFeatures",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--dataset",
|
||||
type=str,
|
||||
default="ljspeech",
|
||||
help="--input-strategy PromptedPrecomputedFeatures needs dataset name to prepare prompts.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--text-tokens",
|
||||
type=str,
|
||||
default="data/tokenized/unique_text_tokens.k2symbols",
|
||||
help="Path to the unique text tokens file",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--sampling-rate",
|
||||
type=int,
|
||||
default=24000,
|
||||
help="""Audio sampling rate.""",
|
||||
)
|
||||
|
||||
def train_dataloaders(
|
||||
self,
|
||||
cuts_train: CutSet,
|
||||
sampler_state_dict: Optional[Dict[str, Any]] = None,
|
||||
) -> DataLoader:
|
||||
"""
|
||||
Args:
|
||||
cuts_train:
|
||||
CutSet for training.
|
||||
sampler_state_dict:
|
||||
The state dict for the training sampler.
|
||||
"""
|
||||
transforms = []
|
||||
|
||||
if self.args.concatenate_cuts:
|
||||
logging.info(
|
||||
f"Using cut concatenation with duration factor "
|
||||
f"{self.args.duration_factor} and gap {self.args.gap}."
|
||||
)
|
||||
# Cut concatenation should be the first transform in the list,
|
||||
# so that if we e.g. mix noise in, it will fill the gaps between
|
||||
# different utterances.
|
||||
transforms = [
|
||||
CutConcatenate(
|
||||
duration_factor=self.args.duration_factor, gap=self.args.gap
|
||||
)
|
||||
] + transforms
|
||||
|
||||
input_transforms = []
|
||||
if self.args.enable_spec_aug:
|
||||
logging.info("Enable SpecAugment")
|
||||
logging.info(
|
||||
f"Time warp factor: {self.args.spec_aug_time_warp_factor}"
|
||||
)
|
||||
# Set the value of num_frame_masks according to Lhotse's version.
|
||||
# In different Lhotse's versions, the default of num_frame_masks is
|
||||
# different.
|
||||
num_frame_masks = 10
|
||||
num_frame_masks_parameter = inspect.signature(
|
||||
SpecAugment.__init__
|
||||
).parameters["num_frame_masks"]
|
||||
if num_frame_masks_parameter.default == 1:
|
||||
num_frame_masks = 2
|
||||
logging.info(f"Num frame mask: {num_frame_masks}")
|
||||
input_transforms.append(
|
||||
SpecAugment(
|
||||
time_warp_factor=self.args.spec_aug_time_warp_factor,
|
||||
num_frame_masks=num_frame_masks,
|
||||
features_mask_size=27,
|
||||
num_feature_masks=2,
|
||||
frames_mask_size=100,
|
||||
)
|
||||
)
|
||||
else:
|
||||
logging.info("Disable SpecAugment")
|
||||
|
||||
logging.info("About to create train dataset")
|
||||
if self.args.on_the_fly_feats:
|
||||
# NOTE: the PerturbSpeed transform should be added only if we
|
||||
# remove it from data prep stage.
|
||||
# Add on-the-fly speed perturbation; since originally it would
|
||||
# have increased epoch size by 3, we will apply prob 2/3 and use
|
||||
# 3x more epochs.
|
||||
# Speed perturbation probably should come first before
|
||||
# concatenation, but in principle the transforms order doesn't have
|
||||
# to be strict (e.g. could be randomized)
|
||||
# transforms = [PerturbSpeed(factors=[0.9, 1.1], p=2/3)] + transforms # noqa
|
||||
# Drop feats to be on the safe side.
|
||||
train = SpeechSynthesisDataset(
|
||||
get_text_token_collater(self.args.text_tokens),
|
||||
cut_transforms=transforms,
|
||||
feature_input_strategy=OnTheFlyFeatures(get_fbank_extractor()),
|
||||
feature_transforms=input_transforms,
|
||||
)
|
||||
else:
|
||||
train = SpeechSynthesisDataset(
|
||||
get_text_token_collater(self.args.text_tokens),
|
||||
feature_input_strategy=_get_input_strategy(
|
||||
self.args.input_strategy, self.args.dataset, cuts_train
|
||||
),
|
||||
cut_transforms=transforms,
|
||||
feature_transforms=input_transforms,
|
||||
)
|
||||
|
||||
if self.args.bucketing_sampler:
|
||||
logging.info("Using DynamicBucketingSampler")
|
||||
train_sampler = DynamicBucketingSampler(
|
||||
cuts_train,
|
||||
max_duration=self.args.max_duration,
|
||||
shuffle=self.args.shuffle,
|
||||
num_buckets=self.args.num_buckets,
|
||||
drop_last=self.args.drop_last,
|
||||
)
|
||||
else:
|
||||
logging.info(
|
||||
"Using SingleCutSampler and sort by duraton(ascending=True)."
|
||||
)
|
||||
cuts_train = cuts_train.to_eager().sort_by_duration(ascending=True)
|
||||
train_sampler = SingleCutSampler(
|
||||
cuts_train,
|
||||
max_duration=self.args.max_duration,
|
||||
shuffle=self.args.shuffle,
|
||||
)
|
||||
logging.info("About to create train dataloader")
|
||||
|
||||
if sampler_state_dict is not None:
|
||||
logging.info("Loading sampler state dict")
|
||||
train_sampler.load_state_dict(sampler_state_dict)
|
||||
|
||||
# 'seed' is derived from the current random state, which will have
|
||||
# previously been set in the main process.
|
||||
seed = torch.randint(0, 100000, ()).item()
|
||||
worker_init_fn = _SeedWorkers(seed)
|
||||
|
||||
train_dl = DataLoader(
|
||||
train,
|
||||
sampler=train_sampler,
|
||||
batch_size=None,
|
||||
num_workers=self.args.num_workers,
|
||||
persistent_workers=False,
|
||||
worker_init_fn=worker_init_fn,
|
||||
)
|
||||
|
||||
return train_dl
|
||||
|
||||
def valid_dataloaders(self, cuts_valid: CutSet) -> DataLoader:
|
||||
logging.info("About to create dev dataset")
|
||||
if self.args.on_the_fly_feats:
|
||||
validate = SpeechSynthesisDataset(
|
||||
get_text_token_collater(self.args.text_tokens),
|
||||
feature_input_strategy=OnTheFlyFeatures(get_fbank_extractor()),
|
||||
cut_transforms=[],
|
||||
)
|
||||
else:
|
||||
validate = SpeechSynthesisDataset(
|
||||
get_text_token_collater(self.args.text_tokens),
|
||||
feature_input_strategy=_get_input_strategy(
|
||||
self.args.input_strategy, self.args.dataset, cuts_valid
|
||||
),
|
||||
cut_transforms=[],
|
||||
)
|
||||
valid_sampler = DynamicBucketingSampler(
|
||||
cuts_valid,
|
||||
max_duration=self.args.max_duration,
|
||||
shuffle=False,
|
||||
)
|
||||
logging.info("About to create dev dataloader")
|
||||
valid_dl = DataLoader(
|
||||
validate,
|
||||
sampler=valid_sampler,
|
||||
batch_size=None,
|
||||
num_workers=4,
|
||||
persistent_workers=False,
|
||||
)
|
||||
|
||||
return valid_dl
|
||||
|
||||
def test_dataloaders(self, cuts: CutSet) -> DataLoader:
|
||||
logging.debug("About to create test dataset")
|
||||
test = SpeechSynthesisDataset(
|
||||
get_text_token_collater(self.args.text_tokens),
|
||||
feature_input_strategy=OnTheFlyFeatures(get_fbank_extractor())
|
||||
if self.args.on_the_fly_feats
|
||||
else _get_input_strategy(
|
||||
self.args.input_strategy, self.args.dataset, cuts
|
||||
),
|
||||
cut_transforms=[],
|
||||
)
|
||||
sampler = DynamicBucketingSampler(
|
||||
cuts,
|
||||
max_duration=self.args.max_duration,
|
||||
shuffle=False,
|
||||
)
|
||||
logging.debug("About to create test dataloader")
|
||||
test_dl = DataLoader(
|
||||
test,
|
||||
batch_size=None,
|
||||
sampler=sampler,
|
||||
num_workers=self.args.num_workers,
|
||||
)
|
||||
return test_dl
|
||||
|
||||
@lru_cache()
|
||||
def train_cuts(self) -> CutSet:
|
||||
logging.info("About to get train cuts")
|
||||
return load_manifest_lazy(
|
||||
self.args.manifest_dir / "cuts_train.jsonl.gz"
|
||||
)
|
||||
|
||||
@lru_cache()
|
||||
def dev_cuts(self) -> CutSet:
|
||||
logging.info("About to get dev cuts")
|
||||
return load_manifest_lazy(self.args.manifest_dir / "cuts_dev.jsonl.gz")
|
||||
|
||||
@lru_cache()
|
||||
def test_cuts(self) -> CutSet:
|
||||
logging.info("About to get test cuts")
|
||||
return load_manifest_lazy(self.args.manifest_dir / "cuts_test.jsonl.gz")
|
||||
@@ -0,0 +1,242 @@
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
modified from lhoste.dataset.speech_synthesis.py
|
||||
"""
|
||||
|
||||
import torch
|
||||
import math
|
||||
import h5py
|
||||
from tokenizers import Tokenizer
|
||||
from typing import Union, List
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
_pad = '_'
|
||||
_punctuation = ',.!?-~…'
|
||||
_letters = 'NQabdefghijklmnopstuvwxyzɑæʃʑçɯɪɔɛɹðəɫɥɸʊɾʒθβŋɦ⁼ʰ`^#*=ˈˌ→↓↑ '
|
||||
symbols = [_pad] + list(_punctuation) + list(_letters)
|
||||
|
||||
language_dict = {
|
||||
'en': 0,
|
||||
'zh': 1,
|
||||
'ja': 2,
|
||||
}
|
||||
def seq2phone(tokens: Union[List, np.ndarray]):
|
||||
"""
|
||||
Convert tokenized phoneme ID sequence back to phoneme string
|
||||
:param tokens: phoneme tokens
|
||||
:return: recovered phoneme sequence
|
||||
"""
|
||||
phones = "".join([symbols[i] for i in tokens])
|
||||
return phones
|
||||
|
||||
class DynamicBatchSampler(torch.utils.data.Sampler):
|
||||
def __init__(self, sampler, num_tokens_fn, num_buckets=100, min_size=0, max_size=1000,
|
||||
max_tokens=None, max_sentences=None, drop_last=False):
|
||||
"""
|
||||
|
||||
:param sampler:
|
||||
:param num_tokens_fn: 根据idx返回样本的长度的函数
|
||||
:param num_buckets: 利用桶原理将相似长度的样本放在一个batchsize中,桶的数量
|
||||
:param min_size: 最小长度的样本, 小于这个值的样本会被过滤掉。 依据这个值来创建样桶
|
||||
:param max_size: 最大长度的样本
|
||||
:param max_sentences: batch_size, 但是这里可以通过max_sentences 和 max_tokens 共同控制最终的大小
|
||||
"""
|
||||
super(DynamicBatchSampler, self).__init__(sampler)
|
||||
self.sampler = sampler
|
||||
self.num_tokens_fn = num_tokens_fn
|
||||
self.num_buckets = num_buckets
|
||||
|
||||
self.min_size = min_size
|
||||
self.max_size = max_size
|
||||
|
||||
assert max_size <= max_tokens, "max_size should be smaller than max tokens"
|
||||
assert max_tokens is not None or max_sentences is not None, \
|
||||
"max_tokens and max_sentences should not be null at the same time, please specify one parameter at least"
|
||||
self.max_tokens = max_tokens if max_tokens is not None else float('Inf')
|
||||
self.max_sentences = max_sentences if max_sentences is not None else float('Inf')
|
||||
self.drop_last = drop_last
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.sampler.set_epoch(epoch)
|
||||
def is_batch_full(self, num_tokens, batch):
|
||||
if len(batch) == 0:
|
||||
return False
|
||||
if len(batch) == self.max_sentences:
|
||||
return True
|
||||
if num_tokens > self.max_tokens:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
buckets = [[] for _ in range(self.num_buckets)]
|
||||
sample_len = [0] * self.num_buckets
|
||||
|
||||
for idx in self.sampler:
|
||||
idx_length = self.num_tokens_fn(idx)
|
||||
if not (self.min_size <= idx_length <= self.max_size):
|
||||
print("sentence at index {} of size {} exceeds max_tokens, the sentence is ignored".format(idx, idx_length))
|
||||
continue
|
||||
|
||||
index_buckets = math.floor((idx_length - self.min_size) / (self.max_size - self.min_size + 1)
|
||||
* self.num_buckets)
|
||||
sample_len[index_buckets] = max(sample_len[index_buckets], idx_length)
|
||||
|
||||
num_tokens = (len(buckets[index_buckets]) + 1) * sample_len[index_buckets]
|
||||
if self.is_batch_full(num_tokens, buckets[index_buckets]):
|
||||
# yield this batch
|
||||
yield buckets[index_buckets]
|
||||
buckets[index_buckets] = []
|
||||
sample_len[index_buckets] = 0
|
||||
|
||||
buckets[index_buckets].append(idx)
|
||||
|
||||
# process left-over
|
||||
leftover_batch = []
|
||||
leftover_sample_len = 0
|
||||
leftover = [idx for bucket in buckets for idx in bucket]
|
||||
for idx in leftover:
|
||||
idx_length = self.num_tokens_fn(idx)
|
||||
leftover_sample_len = max(leftover_sample_len, idx_length)
|
||||
num_tokens = (len(leftover_batch) + 1) * leftover_sample_len
|
||||
if self.is_batch_full(num_tokens, leftover_batch):
|
||||
yield leftover_batch
|
||||
leftover_batch = []
|
||||
leftover_sample_len = 0
|
||||
leftover_batch.append(idx)
|
||||
|
||||
if len(leftover_batch) > 0 and not self.drop_last:
|
||||
yield leftover_batch
|
||||
|
||||
def __len__(self):
|
||||
# we do not know the exactly batch size, so do not call len(dataloader)
|
||||
pass
|
||||
|
||||
|
||||
class AudioDataset(torch.utils.data.Dataset):
|
||||
def __init__(self, h5_path, ann_path, tokenizer_path):
|
||||
self.h5_path = h5_path
|
||||
with open(ann_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
ls = [l.split("|") for l in lines]
|
||||
ls_T = list(zip(*ls))
|
||||
del ls_T[-1]
|
||||
self.h5_paths, self.durations, self.langs, self.texts = \
|
||||
list(ls_T[0]), list(ls_T[1]), list(ls_T[2]), list(ls_T[3])
|
||||
self.durations = [float(dur) for dur in self.durations]
|
||||
self.tokenizer = Tokenizer.from_file(tokenizer_path)
|
||||
|
||||
self._archive = None
|
||||
|
||||
def __len__(self):
|
||||
return len(self.h5_paths)
|
||||
|
||||
def get_dur(self, idx):
|
||||
return self.durations[idx]
|
||||
|
||||
@property
|
||||
def archive(self):
|
||||
if self._archive is None: # lazy loading here!
|
||||
self._archive = h5py.File(self.h5_path, "r")
|
||||
return self._archive
|
||||
def __getitem__(self, idx):
|
||||
archive = self.archive
|
||||
h5_path = self.h5_paths[idx]
|
||||
sub = archive[h5_path]
|
||||
audio_tokens = sub['audio'][()]
|
||||
phone_tokens = sub['text'][()]
|
||||
dur = self.durations[idx]
|
||||
lang = self.langs[idx]
|
||||
text = self.texts[idx]
|
||||
# tokenization should be done within dataloader
|
||||
phones = seq2phone(phone_tokens)
|
||||
phones = phones.replace(" ", "_")
|
||||
if not len(phones):
|
||||
cptpho_tokens = self.tokenizer.encode(text).ids
|
||||
else:
|
||||
cptpho_tokens = self.tokenizer.encode(phones).ids
|
||||
assert len(cptpho_tokens)
|
||||
return {
|
||||
'utt_id': h5_path,
|
||||
'text': text,
|
||||
'audio': None,
|
||||
'audio_lens': None,
|
||||
'audio_features': audio_tokens,
|
||||
'audio_features_lens': len(audio_tokens.T),
|
||||
'text_tokens': np.array(cptpho_tokens),
|
||||
'text_tokens_lens': len(cptpho_tokens),
|
||||
'language': language_dict[lang],
|
||||
}
|
||||
|
||||
def collate(batch):
|
||||
utt_id_s = [b['utt_id'] for b in batch]
|
||||
text_s = [b['text'] for b in batch]
|
||||
|
||||
audio_s = [b['audio'] for b in batch]
|
||||
audio_lens_s = [b['audio_lens'] for b in batch]
|
||||
|
||||
audio_features_lens_s = [b['audio_features_lens'] for b in batch]
|
||||
# create an empty tensor with maximum audio feature length
|
||||
audio_features_s = torch.zeros([len(batch), max(audio_features_lens_s), 8], dtype=torch.int64) - 1 # audio pad with -1
|
||||
|
||||
text_tokens_lens_s = [b['text_tokens_lens'] for b in batch]
|
||||
# create an empty tensor with maximum text tokens length
|
||||
text_tokens_s = torch.zeros([len(batch), max(text_tokens_lens_s)], dtype=torch.int64) + 3 # [PAD] token id 3
|
||||
|
||||
language_s = [b['language'] for b in batch]
|
||||
|
||||
for i, b in enumerate(batch):
|
||||
audio_features = b['audio_features']
|
||||
audio_features_lens = b['audio_features_lens']
|
||||
audio_features_s[i, :audio_features_lens, :] = torch.LongTensor(audio_features.T)
|
||||
|
||||
text_tokens = b['text_tokens']
|
||||
text_tokens_lens = b['text_tokens_lens']
|
||||
text_tokens_s[i, :text_tokens_lens] = torch.LongTensor(text_tokens)
|
||||
|
||||
batch = {
|
||||
'utt_id': utt_id_s,
|
||||
'text': text_s,
|
||||
'audio': audio_s,
|
||||
'audio_lens': audio_lens_s,
|
||||
'audio_features': audio_features_s,
|
||||
'audio_features_lens': torch.LongTensor(np.array(audio_features_lens_s)),
|
||||
'text_tokens': text_tokens_s,
|
||||
'text_tokens_lens': torch.LongTensor(np.array(text_tokens_lens_s)),
|
||||
'languages': torch.LongTensor(np.array(language_s)),
|
||||
}
|
||||
return batch
|
||||
|
||||
def create_dataloader(data_dir="/root/valle/egs/mix", n_gpus=1, rank=0, num_workers=0, num_buckets=10, max_duration=120):
|
||||
train_dataset = AudioDataset(h5_path=f"{data_dir}/audio_sum.hdf5",
|
||||
ann_path=f"{data_dir}/audio_ann_sum.txt",
|
||||
tokenizer_path=f"{data_dir}/bpe_69.json")
|
||||
ran_sampler = torch.utils.data.distributed.DistributedSampler(
|
||||
train_dataset,
|
||||
num_replicas=n_gpus,
|
||||
rank=rank,
|
||||
shuffle=True,
|
||||
)
|
||||
dynamic_sampler = DynamicBatchSampler(ran_sampler, train_dataset.get_dur, num_buckets=num_buckets, max_size=20,
|
||||
max_tokens=max_duration)
|
||||
|
||||
|
||||
train_loader = torch.utils.data.DataLoader(train_dataset, num_workers=num_workers, collate_fn=collate,
|
||||
batch_sampler=dynamic_sampler)
|
||||
|
||||
return train_loader
|
||||
@@ -0,0 +1,212 @@
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
# from lhotse.features.base import FeatureExtractor
|
||||
# from lhotse.utils import EPSILON, Seconds, compute_num_frames
|
||||
from librosa.filters import mel as librosa_mel_fn
|
||||
|
||||
|
||||
@dataclass
|
||||
class BigVGANFbankConfig:
|
||||
# Spectogram-related part
|
||||
# Note that frame_length and frame_shift will be converted to milliseconds before torchaudio/Kaldi sees them
|
||||
frame_length: Seconds = 1024 / 24000.0
|
||||
frame_shift: Seconds = 256 / 24000.0
|
||||
remove_dc_offset: bool = True
|
||||
round_to_power_of_two: bool = True
|
||||
|
||||
# Fbank-related part
|
||||
low_freq: float = 0.0
|
||||
high_freq: float = 12000.0
|
||||
num_mel_bins: int = 100
|
||||
use_energy: bool = False
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: Dict[str, Any]) -> "BigVGANFbankConfig":
|
||||
return BigVGANFbankConfig(**data)
|
||||
|
||||
|
||||
def dynamic_range_compression_torch(x, C=1, clip_val=1e-5):
|
||||
return torch.log(torch.clamp(x, min=clip_val) * C)
|
||||
|
||||
|
||||
def spectral_normalize_torch(magnitudes):
|
||||
output = dynamic_range_compression_torch(magnitudes)
|
||||
return output
|
||||
|
||||
|
||||
# https://github.com/NVIDIA/BigVGAN
|
||||
# bigvgan_24khz_100band https://drive.google.com/drive/folders/1EpxX6AsxjCbbk0mmAhE0td6eYiABr8Oz
|
||||
class BigVGANFbank(FeatureExtractor):
|
||||
name = "fbank"
|
||||
config_type = BigVGANFbankConfig
|
||||
|
||||
def __init__(self, config: Optional[Any] = None):
|
||||
super(BigVGANFbank, self).__init__(config)
|
||||
sampling_rate = 24000
|
||||
self.mel_basis = torch.from_numpy(
|
||||
librosa_mel_fn(
|
||||
sampling_rate,
|
||||
1024,
|
||||
self.config.num_mel_bins,
|
||||
self.config.low_freq,
|
||||
self.config.high_freq,
|
||||
).astype(np.float32)
|
||||
)
|
||||
self.hann_window = torch.hann_window(1024)
|
||||
|
||||
def _feature_fn(self, samples, **kwargs):
|
||||
win_length, n_fft = 1024, 1024
|
||||
hop_size = 256
|
||||
if True:
|
||||
sampling_rate = 24000
|
||||
duration = round(samples.shape[-1] / sampling_rate, ndigits=12)
|
||||
expected_num_frames = compute_num_frames(
|
||||
duration=duration,
|
||||
frame_shift=self.frame_shift,
|
||||
sampling_rate=sampling_rate,
|
||||
)
|
||||
pad_size = (
|
||||
(expected_num_frames - 1) * hop_size
|
||||
+ win_length
|
||||
- samples.shape[-1]
|
||||
)
|
||||
assert pad_size >= 0
|
||||
|
||||
y = torch.nn.functional.pad(
|
||||
samples,
|
||||
(0, pad_size),
|
||||
mode="constant",
|
||||
)
|
||||
else:
|
||||
y = torch.nn.functional.pad(
|
||||
samples,
|
||||
(int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)),
|
||||
mode="reflect",
|
||||
)
|
||||
|
||||
y = y.squeeze(1)
|
||||
|
||||
# complex tensor as default, then use view_as_real for future pytorch compatibility
|
||||
spec = torch.stft(
|
||||
y,
|
||||
n_fft,
|
||||
hop_length=hop_size,
|
||||
win_length=win_length,
|
||||
window=self.hann_window,
|
||||
center=False,
|
||||
pad_mode="reflect",
|
||||
normalized=False,
|
||||
onesided=True,
|
||||
return_complex=True,
|
||||
)
|
||||
spec = torch.view_as_real(spec)
|
||||
spec = torch.sqrt(spec.pow(2).sum(-1) + (1e-9))
|
||||
|
||||
spec = torch.matmul(self.mel_basis, spec)
|
||||
spec = spectral_normalize_torch(spec)
|
||||
|
||||
return spec.transpose(2, 1).squeeze(0)
|
||||
|
||||
def extract(
|
||||
self, samples: Union[np.ndarray, torch.Tensor], sampling_rate: int
|
||||
) -> np.ndarray:
|
||||
assert sampling_rate == 24000
|
||||
params = asdict(self.config)
|
||||
params.update({"sample_frequency": sampling_rate, "snip_edges": False})
|
||||
params["frame_shift"] *= 1000.0
|
||||
params["frame_length"] *= 1000.0
|
||||
if not isinstance(samples, torch.Tensor):
|
||||
samples = torch.from_numpy(samples)
|
||||
# Torchaudio Kaldi feature extractors expect the channel dimension to be first.
|
||||
if len(samples.shape) == 1:
|
||||
samples = samples.unsqueeze(0)
|
||||
features = self._feature_fn(samples, **params).to(torch.float32)
|
||||
return features.numpy()
|
||||
|
||||
@property
|
||||
def frame_shift(self) -> Seconds:
|
||||
return self.config.frame_shift
|
||||
|
||||
def feature_dim(self, sampling_rate: int) -> int:
|
||||
return self.config.num_mel_bins
|
||||
|
||||
@staticmethod
|
||||
def mix(
|
||||
features_a: np.ndarray,
|
||||
features_b: np.ndarray,
|
||||
energy_scaling_factor_b: float,
|
||||
) -> np.ndarray:
|
||||
return np.log(
|
||||
np.maximum(
|
||||
# protection against log(0); max with EPSILON is adequate since these are energies (always >= 0)
|
||||
EPSILON,
|
||||
np.exp(features_a)
|
||||
+ energy_scaling_factor_b * np.exp(features_b),
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def compute_energy(features: np.ndarray) -> float:
|
||||
return float(np.sum(np.exp(features)))
|
||||
|
||||
|
||||
def get_fbank_extractor() -> BigVGANFbank:
|
||||
return BigVGANFbank(BigVGANFbankConfig())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
extractor = BigVGANFbank(BigVGANFbankConfig())
|
||||
|
||||
samples = torch.from_numpy(np.random.random([1000]).astype(np.float32))
|
||||
samples = torch.clip(samples, -1.0, 1.0)
|
||||
fbank = extractor.extract(samples, 24000.0)
|
||||
print(f"fbank {fbank.shape}")
|
||||
|
||||
from scipy.io.wavfile import read
|
||||
|
||||
MAX_WAV_VALUE = 32768.0
|
||||
|
||||
sampling_rate, samples = read(
|
||||
"egs/libritts/prompts/5639_40744_000000_000002.wav"
|
||||
)
|
||||
print(f"samples: [{samples.min()}, {samples.max()}]")
|
||||
fbank = extractor.extract(samples.astype(np.float32) / MAX_WAV_VALUE, 24000)
|
||||
print(f"fbank {fbank.shape}")
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
_ = plt.figure(figsize=(18, 10))
|
||||
plt.imshow(
|
||||
X=fbank.transpose(1, 0),
|
||||
cmap=plt.get_cmap("jet"),
|
||||
aspect="auto",
|
||||
interpolation="nearest",
|
||||
)
|
||||
plt.gca().invert_yaxis()
|
||||
plt.savefig("egs/libritts/prompts/5639_40744_000000_000002.png")
|
||||
plt.close()
|
||||
|
||||
print("fbank test PASS!")
|
||||
@@ -0,0 +1,159 @@
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Tuple, Type
|
||||
|
||||
# from lhotse import CutSet
|
||||
# from lhotse.dataset.collation import collate_features
|
||||
# from lhotse.dataset.input_strategies import (
|
||||
# ExecutorType,
|
||||
# PrecomputedFeatures,
|
||||
# _get_executor,
|
||||
# )
|
||||
# from lhotse.utils import fastcopy
|
||||
|
||||
|
||||
class PromptedFeatures:
|
||||
def __init__(self, prompts, features):
|
||||
self.prompts = prompts
|
||||
self.features = features
|
||||
|
||||
def to(self, device):
|
||||
return PromptedFeatures(
|
||||
self.prompts.to(device), self.features.to(device)
|
||||
)
|
||||
|
||||
def sum(self):
|
||||
return self.features.sum()
|
||||
|
||||
@property
|
||||
def ndim(self):
|
||||
return self.features.ndim
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return (self.prompts, self.features)
|
||||
|
||||
|
||||
# class PromptedPrecomputedFeatures(PrecomputedFeatures):
|
||||
# """
|
||||
# :class:`InputStrategy` that reads pre-computed features, whose manifests
|
||||
# are attached to cuts, from disk.
|
||||
#
|
||||
# It automatically pads the feature matrices with pre or post feature.
|
||||
#
|
||||
# .. automethod:: __call__
|
||||
# """
|
||||
#
|
||||
# def __init__(
|
||||
# self,
|
||||
# dataset: str,
|
||||
# cuts: CutSet,
|
||||
# num_workers: int = 0,
|
||||
# executor_type: Type[ExecutorType] = ThreadPoolExecutor,
|
||||
# ) -> None:
|
||||
# super(PromptedPrecomputedFeatures, self).__init__(
|
||||
# num_workers, executor_type
|
||||
# )
|
||||
#
|
||||
# self.utt2neighbors = defaultdict(lambda: [])
|
||||
#
|
||||
# if dataset.lower() == "libritts":
|
||||
# # 909_131041_000013_000002
|
||||
# # 909_131041_000013_000003
|
||||
# speaker2utts = defaultdict(lambda: [])
|
||||
#
|
||||
# utt2cut = {}
|
||||
# for cut in cuts:
|
||||
# speaker = cut.supervisions[0].speaker
|
||||
# speaker2utts[speaker].append(cut.id)
|
||||
# utt2cut[cut.id] = cut
|
||||
#
|
||||
# for spk in speaker2utts:
|
||||
# uttids = sorted(speaker2utts[spk])
|
||||
# # Using the property of sorted keys to find previous utterance
|
||||
# # The keys has structure speaker_book_x_y e.g. 1089_134691_000004_000001
|
||||
# if len(uttids) == 1:
|
||||
# self.utt2neighbors[uttids[0]].append(utt2cut[uttids[0]])
|
||||
# continue
|
||||
#
|
||||
# utt2prevutt = dict(zip(uttids, [uttids[1]] + uttids[:-1]))
|
||||
# utt2postutt = dict(zip(uttids[:-1], uttids[1:]))
|
||||
#
|
||||
# for utt in utt2prevutt:
|
||||
# self.utt2neighbors[utt].append(utt2cut[utt2prevutt[utt]])
|
||||
#
|
||||
# for utt in utt2postutt:
|
||||
# self.utt2neighbors[utt].append(utt2cut[utt2postutt[utt]])
|
||||
# elif dataset.lower() == "ljspeech":
|
||||
# utt2cut = {}
|
||||
# uttids = []
|
||||
# for cut in cuts:
|
||||
# uttids.append(cut.id)
|
||||
# utt2cut[cut.id] = cut
|
||||
#
|
||||
# if len(uttids) == 1:
|
||||
# self.utt2neighbors[uttids[0]].append(utt2cut[uttids[0]])
|
||||
# else:
|
||||
# # Using the property of sorted keys to find previous utterance
|
||||
# # The keys has structure: LJ001-0010
|
||||
# utt2prevutt = dict(zip(uttids, [uttids[1]] + uttids[:-1]))
|
||||
# utt2postutt = dict(zip(uttids[:-1], uttids[1:]))
|
||||
#
|
||||
# for utt in utt2postutt:
|
||||
# postutt = utt2postutt[utt]
|
||||
# if utt[:5] == postutt[:5]:
|
||||
# self.utt2neighbors[utt].append(utt2cut[postutt])
|
||||
#
|
||||
# for utt in utt2prevutt:
|
||||
# prevutt = utt2prevutt[utt]
|
||||
# if utt[:5] == prevutt[:5] or not self.utt2neighbors[utt]:
|
||||
# self.utt2neighbors[utt].append(utt2cut[prevutt])
|
||||
# else:
|
||||
# raise ValueError
|
||||
#
|
||||
# def __call__(
|
||||
# self, cuts: CutSet
|
||||
# ) -> Tuple[PromptedFeatures, PromptedFeatures]:
|
||||
# """
|
||||
# Reads the pre-computed features from disk/other storage.
|
||||
# The returned shape is``(B, T, F) => (batch_size, num_frames, num_features)``.
|
||||
#
|
||||
# :return: a tensor with collated features, and a tensor of ``num_frames`` of each cut before padding.
|
||||
# """
|
||||
# features, features_lens = collate_features(
|
||||
# cuts,
|
||||
# executor=_get_executor(
|
||||
# self.num_workers, executor_type=self._executor_type
|
||||
# ),
|
||||
# )
|
||||
#
|
||||
# prompts_cuts = []
|
||||
# for k, cut in enumerate(cuts):
|
||||
# prompts_cut = random.choice(self.utt2neighbors[cut.id])
|
||||
# prompts_cuts.append(fastcopy(prompts_cut, id=f"{cut.id}-{str(k)}"))
|
||||
#
|
||||
# mini_duration = min([cut.duration for cut in prompts_cuts] + [3.0])
|
||||
# # prompts_cuts = CutSet.from_cuts(prompts_cuts).truncate(
|
||||
# # max_duration=mini_duration,
|
||||
# # offset_type="random",
|
||||
# # preserve_id=True,
|
||||
# # )
|
||||
# prompts_cuts = CutSet(
|
||||
# cuts={k: cut for k, cut in enumerate(prompts_cuts)}
|
||||
# ).truncate(
|
||||
# max_duration=mini_duration,
|
||||
# offset_type="random",
|
||||
# preserve_id=False,
|
||||
# )
|
||||
#
|
||||
# prompts, prompts_lens = collate_features(
|
||||
# prompts_cuts,
|
||||
# executor=_get_executor(
|
||||
# self.num_workers, executor_type=self._executor_type
|
||||
# ),
|
||||
# )
|
||||
#
|
||||
# return PromptedFeatures(prompts, features), PromptedFeatures(
|
||||
# prompts_lens, features_lens
|
||||
# )
|
||||
@@ -0,0 +1,376 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import Any, Dict, List, Optional, Pattern, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torchaudio
|
||||
from encodec import EncodecModel
|
||||
from encodec.utils import convert_audio
|
||||
from phonemizer.backend import EspeakBackend
|
||||
from phonemizer.backend.espeak.language_switch import LanguageSwitch
|
||||
from phonemizer.backend.espeak.words_mismatch import WordMismatch
|
||||
from phonemizer.punctuation import Punctuation
|
||||
from phonemizer.separator import Separator
|
||||
|
||||
try:
|
||||
from pypinyin import Style, pinyin
|
||||
from pypinyin.style._utils import get_finals, get_initials
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class PypinyinBackend:
|
||||
"""PypinyinBackend for Chinese. Most codes is referenced from espnet.
|
||||
There are two types pinyin or initials_finals, one is
|
||||
just like "ni1 hao3", the other is like "n i1 h ao3".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
backend="initials_finals",
|
||||
punctuation_marks: Union[str, Pattern] = Punctuation.default_marks(),
|
||||
) -> None:
|
||||
self.backend = backend
|
||||
self.punctuation_marks = punctuation_marks
|
||||
|
||||
def phonemize(
|
||||
self, text: List[str], separator: Separator, strip=True, njobs=1
|
||||
) -> List[str]:
|
||||
assert isinstance(text, List)
|
||||
phonemized = []
|
||||
for _text in text:
|
||||
_text = re.sub(" +", " ", _text.strip())
|
||||
_text = _text.replace(" ", separator.word)
|
||||
phones = []
|
||||
if self.backend == "pypinyin":
|
||||
for n, py in enumerate(
|
||||
pinyin(
|
||||
_text, style=Style.TONE3, neutral_tone_with_five=True
|
||||
)
|
||||
):
|
||||
if all([c in self.punctuation_marks for c in py[0]]):
|
||||
if len(phones):
|
||||
assert phones[-1] == separator.syllable
|
||||
phones.pop(-1)
|
||||
|
||||
phones.extend(list(py[0]))
|
||||
else:
|
||||
phones.extend([py[0], separator.syllable])
|
||||
elif self.backend == "pypinyin_initials_finals":
|
||||
for n, py in enumerate(
|
||||
pinyin(
|
||||
_text, style=Style.TONE3, neutral_tone_with_five=True
|
||||
)
|
||||
):
|
||||
if all([c in self.punctuation_marks for c in py[0]]):
|
||||
if len(phones):
|
||||
assert phones[-1] == separator.syllable
|
||||
phones.pop(-1)
|
||||
phones.extend(list(py[0]))
|
||||
else:
|
||||
if py[0][-1].isalnum():
|
||||
initial = get_initials(py[0], strict=False)
|
||||
if py[0][-1].isdigit():
|
||||
final = (
|
||||
get_finals(py[0][:-1], strict=False)
|
||||
+ py[0][-1]
|
||||
)
|
||||
else:
|
||||
final = get_finals(py[0], strict=False)
|
||||
phones.extend(
|
||||
[
|
||||
initial,
|
||||
separator.phone,
|
||||
final,
|
||||
separator.syllable,
|
||||
]
|
||||
)
|
||||
else:
|
||||
assert ValueError
|
||||
else:
|
||||
raise NotImplementedError
|
||||
phonemized.append(
|
||||
"".join(phones).rstrip(f"{separator.word}{separator.syllable}")
|
||||
)
|
||||
return phonemized
|
||||
|
||||
|
||||
class TextTokenizer:
|
||||
"""Phonemize Text."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
language="en-us",
|
||||
backend="espeak",
|
||||
separator=Separator(word="_", syllable="-", phone="|"),
|
||||
preserve_punctuation=True,
|
||||
punctuation_marks: Union[str, Pattern] = Punctuation.default_marks(),
|
||||
with_stress: bool = False,
|
||||
tie: Union[bool, str] = False,
|
||||
language_switch: LanguageSwitch = "keep-flags",
|
||||
words_mismatch: WordMismatch = "ignore",
|
||||
) -> None:
|
||||
if backend == "espeak":
|
||||
phonemizer = EspeakBackend(
|
||||
language,
|
||||
punctuation_marks=punctuation_marks,
|
||||
preserve_punctuation=preserve_punctuation,
|
||||
with_stress=with_stress,
|
||||
tie=tie,
|
||||
language_switch=language_switch,
|
||||
words_mismatch=words_mismatch,
|
||||
)
|
||||
elif backend in ["pypinyin", "pypinyin_initials_finals"]:
|
||||
phonemizer = PypinyinBackend(
|
||||
backend=backend,
|
||||
punctuation_marks=punctuation_marks + separator.word,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"{backend}")
|
||||
|
||||
self.backend = phonemizer
|
||||
self.separator = separator
|
||||
|
||||
def to_list(self, phonemized: str) -> List[str]:
|
||||
fields = []
|
||||
for word in phonemized.split(self.separator.word):
|
||||
# "ɐ m|iː|n?" ɹ|ɪ|z|ɜː|v; h|ɪ|z.
|
||||
pp = re.findall(r"\w+|[^\w\s]", word, re.UNICODE)
|
||||
fields.extend(
|
||||
[p for p in pp if p != self.separator.phone]
|
||||
+ [self.separator.word]
|
||||
)
|
||||
assert len("".join(fields[:-1])) == len(phonemized) - phonemized.count(
|
||||
self.separator.phone
|
||||
)
|
||||
return fields[:-1]
|
||||
|
||||
def __call__(self, text, strip=True) -> List[List[str]]:
|
||||
if isinstance(text, str):
|
||||
text = [text]
|
||||
|
||||
phonemized = self.backend.phonemize(
|
||||
text, separator=self.separator, strip=strip, njobs=1
|
||||
)
|
||||
return [self.to_list(p) for p in phonemized]
|
||||
|
||||
|
||||
def tokenize_text(tokenizer: TextTokenizer, text: str) -> List[str]:
|
||||
phonemes = tokenizer([text.strip()])
|
||||
return phonemes[0] # k2symbols
|
||||
|
||||
|
||||
def remove_encodec_weight_norm(model):
|
||||
from encodec.modules import SConv1d
|
||||
from encodec.modules.seanet import SConvTranspose1d, SEANetResnetBlock
|
||||
from torch.nn.utils import remove_weight_norm
|
||||
|
||||
encoder = model.encoder.model
|
||||
for key in encoder._modules:
|
||||
if isinstance(encoder._modules[key], SEANetResnetBlock):
|
||||
remove_weight_norm(encoder._modules[key].shortcut.conv.conv)
|
||||
block_modules = encoder._modules[key].block._modules
|
||||
for skey in block_modules:
|
||||
if isinstance(block_modules[skey], SConv1d):
|
||||
remove_weight_norm(block_modules[skey].conv.conv)
|
||||
elif isinstance(encoder._modules[key], SConv1d):
|
||||
remove_weight_norm(encoder._modules[key].conv.conv)
|
||||
|
||||
decoder = model.decoder.model
|
||||
for key in decoder._modules:
|
||||
if isinstance(decoder._modules[key], SEANetResnetBlock):
|
||||
remove_weight_norm(decoder._modules[key].shortcut.conv.conv)
|
||||
block_modules = decoder._modules[key].block._modules
|
||||
for skey in block_modules:
|
||||
if isinstance(block_modules[skey], SConv1d):
|
||||
remove_weight_norm(block_modules[skey].conv.conv)
|
||||
elif isinstance(decoder._modules[key], SConvTranspose1d):
|
||||
remove_weight_norm(decoder._modules[key].convtr.convtr)
|
||||
elif isinstance(decoder._modules[key], SConv1d):
|
||||
remove_weight_norm(decoder._modules[key].conv.conv)
|
||||
|
||||
|
||||
class AudioTokenizer:
|
||||
"""EnCodec audio."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: Any = None,
|
||||
) -> None:
|
||||
# Instantiate a pretrained EnCodec model
|
||||
model = EncodecModel.encodec_model_24khz()
|
||||
model.set_target_bandwidth(6.0)
|
||||
remove_encodec_weight_norm(model)
|
||||
|
||||
if not device:
|
||||
device = torch.device("cpu")
|
||||
if torch.cuda.is_available():
|
||||
device = torch.device("cuda:0")
|
||||
|
||||
self._device = device
|
||||
|
||||
self.codec = model.to(device)
|
||||
self.sample_rate = model.sample_rate
|
||||
self.channels = model.channels
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
return self._device
|
||||
|
||||
def encode(self, wav: torch.Tensor) -> torch.Tensor:
|
||||
return self.codec.encode(wav.to(self.device))
|
||||
|
||||
def decode(self, frames: torch.Tensor) -> torch.Tensor:
|
||||
return self.codec.decode(frames)
|
||||
|
||||
|
||||
def tokenize_audio(tokenizer: AudioTokenizer, audio):
|
||||
# Load and pre-process the audio waveform
|
||||
if isinstance(audio, str):
|
||||
wav, sr = torchaudio.load(audio)
|
||||
else:
|
||||
wav, sr = audio
|
||||
wav = convert_audio(wav, sr, tokenizer.sample_rate, tokenizer.channels)
|
||||
wav = wav.unsqueeze(0)
|
||||
|
||||
# Extract discrete codes from EnCodec
|
||||
with torch.no_grad():
|
||||
encoded_frames = tokenizer.encode(wav)
|
||||
return encoded_frames
|
||||
|
||||
|
||||
# @dataclass
|
||||
# class AudioTokenConfig:
|
||||
# frame_shift: Seconds = 320.0 / 24000
|
||||
# num_quantizers: int = 8
|
||||
#
|
||||
# def to_dict(self) -> Dict[str, Any]:
|
||||
# return asdict(self)
|
||||
#
|
||||
# @staticmethod
|
||||
# def from_dict(data: Dict[str, Any]) -> "AudioTokenConfig":
|
||||
# return AudioTokenConfig(**data)
|
||||
#
|
||||
#
|
||||
# class AudioTokenExtractor(FeatureExtractor):
|
||||
# name = "encodec"
|
||||
# config_type = AudioTokenConfig
|
||||
#
|
||||
# def __init__(self, config: Optional[Any] = None):
|
||||
# super(AudioTokenExtractor, self).__init__(config)
|
||||
# self.tokenizer = AudioTokenizer()
|
||||
#
|
||||
# def extract(
|
||||
# self, samples: Union[np.ndarray, torch.Tensor], sampling_rate: int
|
||||
# ) -> np.ndarray:
|
||||
# if not isinstance(samples, torch.Tensor):
|
||||
# samples = torch.from_numpy(samples)
|
||||
# if sampling_rate != self.tokenizer.sample_rate:
|
||||
# samples = convert_audio(
|
||||
# samples,
|
||||
# sampling_rate,
|
||||
# self.tokenizer.sample_rate,
|
||||
# self.tokenizer.channels,
|
||||
# )
|
||||
# if len(samples.shape) == 2:
|
||||
# samples = samples.unsqueeze(0)
|
||||
# else:
|
||||
# raise ValueError()
|
||||
#
|
||||
# device = self.tokenizer.device
|
||||
# encoded_frames = self.tokenizer.encode(samples.detach().to(device))
|
||||
# codes = encoded_frames[0][0] # [B, n_q, T]
|
||||
# if True:
|
||||
# duration = round(samples.shape[-1] / sampling_rate, ndigits=12)
|
||||
# expected_num_frames = compute_num_frames(
|
||||
# duration=duration,
|
||||
# frame_shift=self.frame_shift,
|
||||
# sampling_rate=sampling_rate,
|
||||
# )
|
||||
# assert abs(codes.shape[-1] - expected_num_frames) <= 1
|
||||
# codes = codes[..., :expected_num_frames]
|
||||
# return codes.cpu().squeeze(0).permute(1, 0).numpy()
|
||||
#
|
||||
# @property
|
||||
# def frame_shift(self) -> Seconds:
|
||||
# return self.config.frame_shift
|
||||
#
|
||||
# def feature_dim(self, sampling_rate: int) -> int:
|
||||
# return self.config.num_quantizers
|
||||
#
|
||||
# def pad_tensor_list(self, tensor_list, device, padding_value=0):
|
||||
# # 计算每个张量的长度
|
||||
# lengths = [tensor.shape[0] for tensor in tensor_list]
|
||||
# # 使用pad_sequence函数进行填充
|
||||
# tensor_list = [torch.Tensor(t).to(device) for t in tensor_list]
|
||||
# padded_tensor = torch.nn.utils.rnn.pad_sequence(
|
||||
# tensor_list, batch_first=True, padding_value=padding_value
|
||||
# )
|
||||
# return padded_tensor, lengths
|
||||
#
|
||||
# def extract_batch(self, samples, sampling_rate, lengths) -> np.ndarray:
|
||||
# samples = [wav.squeeze() for wav in samples]
|
||||
# device = self.tokenizer.device
|
||||
# samples, lengths = self.pad_tensor_list(samples, device)
|
||||
# samples = samples.unsqueeze(1)
|
||||
#
|
||||
# if not isinstance(samples, torch.Tensor):
|
||||
# samples = torch.from_numpy(samples)
|
||||
# if len(samples.shape) != 3:
|
||||
# raise ValueError()
|
||||
# if sampling_rate != self.tokenizer.sample_rate:
|
||||
# samples = [
|
||||
# convert_audio(
|
||||
# wav,
|
||||
# sampling_rate,
|
||||
# self.tokenizer.sample_rate,
|
||||
# self.tokenizer.channels,
|
||||
# )
|
||||
# for wav in samples
|
||||
# ]
|
||||
# # Extract discrete codes from EnCodec
|
||||
# with torch.no_grad():
|
||||
# encoded_frames = self.tokenizer.encode(samples.detach().to(device))
|
||||
# encoded_frames = encoded_frames[0][0] # [B, n_q, T]
|
||||
# batch_codes = []
|
||||
# for b, length in enumerate(lengths):
|
||||
# codes = encoded_frames[b]
|
||||
# duration = round(length / sampling_rate, ndigits=12)
|
||||
# expected_num_frames = compute_num_frames(
|
||||
# duration=duration,
|
||||
# frame_shift=self.frame_shift,
|
||||
# sampling_rate=sampling_rate,
|
||||
# )
|
||||
# batch_codes.append(codes[..., :expected_num_frames])
|
||||
# return [codes.cpu().permute(1, 0).numpy() for codes in batch_codes]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
model = EncodecModel.encodec_model_24khz()
|
||||
model.set_target_bandwidth(6.0)
|
||||
|
||||
samples = torch.from_numpy(np.random.random([4, 1, 1600])).type(
|
||||
torch.float32
|
||||
)
|
||||
codes_raw = model.encode(samples)
|
||||
|
||||
remove_encodec_weight_norm(model)
|
||||
codes_norm = model.encode(samples)
|
||||
|
||||
assert torch.allclose(codes_raw[0][0], codes_norm[0][0])
|
||||
@@ -0,0 +1,31 @@
|
||||
top_md = """
|
||||
# VALL-E X
|
||||
VALL-E X can synthesize high-quality personalized speech with only a 3-second enrolled recording of
|
||||
an unseen speaker as an acoustic prompt, even in another language for a monolingual speaker.<br>
|
||||
This implementation supports zero-shot, mono-lingual/cross-lingual text-to-speech functionality of three languages (English, Chinese, Japanese)<br>
|
||||
See this [demo](https://plachtaa.github.io/) page for more details.
|
||||
"""
|
||||
|
||||
infer_from_audio_md = """
|
||||
Upload a speech of 3~10 seconds as the audio prompt and type in the text you'd like to synthesize.<br>
|
||||
The model will synthesize speech of given text with the same voice of your audio prompt.<br>
|
||||
The model also tends to preserve the emotion & acoustic environment of your given speech.<br>
|
||||
For faster inference, please use **"Make prompt"** to get a `.npz` file as the encoded audio prompt, and use it by **"Infer from prompt"**
|
||||
"""
|
||||
|
||||
make_prompt_md = """
|
||||
Upload a speech of 3~10 seconds as the audio prompt.<br>
|
||||
Get a `.npz` file as the encoded audio prompt. Use it by **"Infer with prompt"**
|
||||
"""
|
||||
|
||||
infer_from_prompt_md = """
|
||||
Faster than **"Infer from audio"**.<br>
|
||||
You need to **"Make prompt"** first, and upload the encoded prompt (a `.npz` file)
|
||||
"""
|
||||
|
||||
long_text_md = """
|
||||
Very long text is chunked into several sentences, and each sentence is synthesized separately.<br>
|
||||
Please make a prompt or use a preset prompt to infer long text.
|
||||
"""
|
||||
|
||||
long_text_example = "Just a few years ago, there were no legions of deep learning scientists developing intelligent products and services at major companies and startups. When we entered the field, machine learning did not command headlines in daily newspapers. Our parents had no idea what machine learning was, let alone why we might prefer it to a career in medicine or law. Machine learning was a blue skies academic discipline whose industrial significance was limited to a narrow set of real-world applications, including speech recognition and computer vision. Moreover, many of these applications required so much domain knowledge that they were often regarded as entirely separate areas for which machine learning was one small component. At that time, neural networks—the predecessors of the deep learning methods that we focus on in this book—were generally regarded as outmoded."
|
||||
@@ -0,0 +1,24 @@
|
||||
infer_from_audio_examples = [
|
||||
["This is how this machine has taken my voice.", 'English', 'no-accent', "prompts/en-2.wav", None, "Wow, look at that! That's no ordinary Teddy bear!"],
|
||||
["我喜欢抽电子烟,尤其是锐刻五代。", '中文', 'no-accent', "prompts/zh-1.wav", None, "今天我很荣幸,"],
|
||||
["私の声を真似するのはそんなに面白いですか?", '日本語', 'no-accent', "prompts/ja-2.ogg", None, "初めまして、朝武よしのです。"],
|
||||
["你可以听得出来我有多困。", '中文', 'no-accent', "prompts/en-1.wav", None, ""],
|
||||
["この文は、クロスリンガル合成の例です。", '日本語', 'no-accent', "prompts/zh-2.wav", None, ""],
|
||||
["Actually, I can't speak English, but this machine helped me do it.", 'English', 'no-accent', "prompts/ja-1.wav", None, ""],
|
||||
]
|
||||
|
||||
make_npz_prompt_examples = [
|
||||
["Gem-trader", "prompts/en-2.wav", None, "Wow, look at that! That's no ordinary Teddy bear!"],
|
||||
["Ding Zhen", "prompts/zh-1.wav", None, "今天我很荣幸,"],
|
||||
["Yoshino", "prompts/ja-2.ogg", None, "初めまして、朝武よしのです。"],
|
||||
["Sleepy-woman", "prompts/en-1.wav", None, ""],
|
||||
["Yae", "prompts/zh-2.wav", None, ""],
|
||||
["Cafe", "prompts/ja-1.wav", None, ""],
|
||||
]
|
||||
|
||||
infer_from_prompt_examples = [
|
||||
["A prompt contains voice, prosody and emotion information of a certain speaker.", "English", "no-accent", "vctk_1", None],
|
||||
["This prompt is made with an audio of three seconds.", "English", "no-accent", "librispeech_1", None],
|
||||
["This prompt is made with Chinese speech", "English", "no-accent", "seel", None],
|
||||
]
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,628 @@
|
||||
# coding: utf-8
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import time
|
||||
import tempfile
|
||||
import platform
|
||||
import webbrowser
|
||||
import sys
|
||||
print(f"default encoding is {sys.getdefaultencoding()},file system encoding is {sys.getfilesystemencoding()}")
|
||||
print(f"You are using Python version {platform.python_version()}")
|
||||
if(sys.version_info[0]<3 or sys.version_info[1]<7):
|
||||
print("The Python version is too low and may cause problems")
|
||||
|
||||
if platform.system().lower() == 'windows':
|
||||
temp = pathlib.PosixPath
|
||||
pathlib.PosixPath = pathlib.WindowsPath
|
||||
else:
|
||||
temp = pathlib.WindowsPath
|
||||
pathlib.WindowsPath = pathlib.PosixPath
|
||||
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
||||
|
||||
import langid
|
||||
langid.set_languages(['en', 'zh', 'ja'])
|
||||
|
||||
import nltk
|
||||
nltk.data.path = nltk.data.path + [os.path.join(os.getcwd(), "nltk_data")]
|
||||
|
||||
import torch
|
||||
import torchaudio
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
|
||||
from data.tokenizer import (
|
||||
AudioTokenizer,
|
||||
tokenize_audio,
|
||||
)
|
||||
from data.collation import get_text_token_collater
|
||||
from models.vallex import VALLE
|
||||
from utils.g2p import PhonemeBpeTokenizer
|
||||
from descriptions import *
|
||||
from macros import *
|
||||
from examples import *
|
||||
|
||||
import gradio as gr
|
||||
import whisper
|
||||
from vocos import Vocos
|
||||
import multiprocessing
|
||||
|
||||
thread_count = multiprocessing.cpu_count()
|
||||
|
||||
print("Use",thread_count,"cpu cores for computing")
|
||||
|
||||
torch.set_num_threads(thread_count)
|
||||
torch.set_num_interop_threads(thread_count)
|
||||
torch._C._jit_set_profiling_executor(False)
|
||||
torch._C._jit_set_profiling_mode(False)
|
||||
torch._C._set_graph_executor_optimize(False)
|
||||
|
||||
text_tokenizer = PhonemeBpeTokenizer(tokenizer_path="./utils/g2p/bpe_69.json")
|
||||
text_collater = get_text_token_collater()
|
||||
|
||||
device = torch.device("cpu")
|
||||
if torch.cuda.is_available():
|
||||
device = torch.device("cuda", 0)
|
||||
|
||||
# VALL-E-X model
|
||||
if not os.path.exists("./checkpoints/"): os.mkdir("./checkpoints/")
|
||||
if not os.path.exists(os.path.join("./checkpoints/", "vallex-checkpoint.pt")):
|
||||
import wget
|
||||
try:
|
||||
logging.info("Downloading model from https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt ...")
|
||||
# download from https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt to ./checkpoints/vallex-checkpoint.pt
|
||||
wget.download("https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt",
|
||||
out="./checkpoints/vallex-checkpoint.pt", bar=wget.bar_adaptive)
|
||||
except Exception as e:
|
||||
logging.info(e)
|
||||
raise Exception(
|
||||
"\n Model weights download failed, please go to 'https://huggingface.co/Plachta/VALL-E-X/resolve/main/vallex-checkpoint.pt'"
|
||||
"\n manually download model weights and put it to {} .".format(os.getcwd() + "\checkpoints"))
|
||||
|
||||
model = VALLE(
|
||||
N_DIM,
|
||||
NUM_HEAD,
|
||||
NUM_LAYERS,
|
||||
norm_first=True,
|
||||
add_prenet=False,
|
||||
prefix_mode=PREFIX_MODE,
|
||||
share_embedding=True,
|
||||
nar_scale_factor=1.0,
|
||||
prepend_bos=True,
|
||||
num_quantizers=NUM_QUANTIZERS,
|
||||
)
|
||||
checkpoint = torch.load("./checkpoints/vallex-checkpoint.pt", map_location='cpu')
|
||||
missing_keys, unexpected_keys = model.load_state_dict(
|
||||
checkpoint["model"], strict=True
|
||||
)
|
||||
assert not missing_keys
|
||||
model.eval()
|
||||
|
||||
# Encodec model
|
||||
audio_tokenizer = AudioTokenizer(device)
|
||||
|
||||
# Vocos decoder
|
||||
vocos = Vocos.from_pretrained('charactr/vocos-encodec-24khz').to(device)
|
||||
|
||||
# ASR
|
||||
if not os.path.exists("./whisper/"): os.mkdir("./whisper/")
|
||||
try:
|
||||
whisper_model = whisper.load_model("medium",download_root=os.path.join(os.getcwd(), "whisper")).cpu()
|
||||
except Exception as e:
|
||||
logging.info(e)
|
||||
raise Exception(
|
||||
"\n Whisper download failed or damaged, please go to "
|
||||
"'https://openaipublic.azureedge.net/main/whisper/models/345ae4da62f9b3d59415adc60127b97c714f32e89e936602e85993674d08dcb1/medium.pt'"
|
||||
"\n manually download model and put it to {} .".format(os.getcwd() + "\whisper"))
|
||||
|
||||
# Voice Presets
|
||||
preset_list = os.walk("./presets/").__next__()[2]
|
||||
preset_list = [preset[:-4] for preset in preset_list if preset.endswith(".npz")]
|
||||
|
||||
def clear_prompts():
|
||||
try:
|
||||
path = tempfile.gettempdir()
|
||||
for eachfile in os.listdir(path):
|
||||
filename = os.path.join(path, eachfile)
|
||||
if os.path.isfile(filename) and filename.endswith(".npz"):
|
||||
lastmodifytime = os.stat(filename).st_mtime
|
||||
endfiletime = time.time() - 60
|
||||
if endfiletime > lastmodifytime:
|
||||
os.remove(filename)
|
||||
except:
|
||||
return
|
||||
|
||||
def transcribe_one(model, audio_path):
|
||||
# load audio and pad/trim it to fit 30 seconds
|
||||
audio = whisper.load_audio(audio_path)
|
||||
audio = whisper.pad_or_trim(audio)
|
||||
|
||||
# make log-Mel spectrogram and move to the same device as the model
|
||||
mel = whisper.log_mel_spectrogram(audio).to(model.device)
|
||||
|
||||
# detect the spoken language
|
||||
_, probs = model.detect_language(mel)
|
||||
print(f"Detected language: {max(probs, key=probs.get)}")
|
||||
lang = max(probs, key=probs.get)
|
||||
# decode the audio
|
||||
options = whisper.DecodingOptions(temperature=1.0, best_of=5, fp16=False if device == torch.device("cpu") else True, sample_len=150)
|
||||
result = whisper.decode(model, mel, options)
|
||||
|
||||
# print the recognized text
|
||||
print(result.text)
|
||||
|
||||
text_pr = result.text
|
||||
if text_pr.strip(" ")[-1] not in "?!.,。,?!。、":
|
||||
text_pr += "."
|
||||
return lang, text_pr
|
||||
|
||||
def make_npz_prompt(name, uploaded_audio, recorded_audio, transcript_content):
|
||||
global model, text_collater, text_tokenizer, audio_tokenizer
|
||||
clear_prompts()
|
||||
audio_prompt = uploaded_audio if uploaded_audio is not None else recorded_audio
|
||||
sr, wav_pr = audio_prompt
|
||||
if not isinstance(wav_pr, torch.FloatTensor):
|
||||
wav_pr = torch.FloatTensor(wav_pr)
|
||||
if wav_pr.abs().max() > 1:
|
||||
wav_pr /= wav_pr.abs().max()
|
||||
if wav_pr.size(-1) == 2:
|
||||
wav_pr = wav_pr[:, 0]
|
||||
if wav_pr.ndim == 1:
|
||||
wav_pr = wav_pr.unsqueeze(0)
|
||||
assert wav_pr.ndim and wav_pr.size(0) == 1
|
||||
|
||||
if transcript_content == "":
|
||||
text_pr, lang_pr = make_prompt(name, wav_pr, sr, save=False)
|
||||
else:
|
||||
lang_pr = langid.classify(str(transcript_content))[0]
|
||||
lang_token = lang2token[lang_pr]
|
||||
text_pr = f"{lang_token}{str(transcript_content)}{lang_token}"
|
||||
# tokenize audio
|
||||
encoded_frames = tokenize_audio(audio_tokenizer, (wav_pr, sr))
|
||||
audio_tokens = encoded_frames[0][0].transpose(2, 1).cpu().numpy()
|
||||
|
||||
# tokenize text
|
||||
phonemes, _ = text_tokenizer.tokenize(text=f"{text_pr}".strip())
|
||||
text_tokens, enroll_x_lens = text_collater(
|
||||
[
|
||||
phonemes
|
||||
]
|
||||
)
|
||||
|
||||
message = f"Detected language: {lang_pr}\n Detected text {text_pr}\n"
|
||||
|
||||
# save as npz file
|
||||
np.savez(os.path.join(tempfile.gettempdir(), f"{name}.npz"),
|
||||
audio_tokens=audio_tokens, text_tokens=text_tokens, lang_code=lang2code[lang_pr])
|
||||
return message, os.path.join(tempfile.gettempdir(), f"{name}.npz")
|
||||
|
||||
|
||||
def make_prompt(name, wav, sr, save=True):
|
||||
global whisper_model
|
||||
whisper_model.to(device)
|
||||
if not isinstance(wav, torch.FloatTensor):
|
||||
wav = torch.tensor(wav)
|
||||
if wav.abs().max() > 1:
|
||||
wav /= wav.abs().max()
|
||||
if wav.size(-1) == 2:
|
||||
wav = wav.mean(-1, keepdim=False)
|
||||
if wav.ndim == 1:
|
||||
wav = wav.unsqueeze(0)
|
||||
assert wav.ndim and wav.size(0) == 1
|
||||
torchaudio.save(f"./prompts/{name}.wav", wav, sr)
|
||||
lang, text = transcribe_one(whisper_model, f"./prompts/{name}.wav")
|
||||
lang_token = lang2token[lang]
|
||||
text = lang_token + text + lang_token
|
||||
with open(f"./prompts/{name}.txt", 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
if not save:
|
||||
os.remove(f"./prompts/{name}.wav")
|
||||
os.remove(f"./prompts/{name}.txt")
|
||||
|
||||
whisper_model.cpu()
|
||||
torch.cuda.empty_cache()
|
||||
return text, lang
|
||||
|
||||
@torch.no_grad()
|
||||
def infer_from_audio(text, language, accent, audio_prompt, record_audio_prompt, transcript_content):
|
||||
global model, text_collater, text_tokenizer, audio_tokenizer
|
||||
audio_prompt = audio_prompt if audio_prompt is not None else record_audio_prompt
|
||||
sr, wav_pr = audio_prompt
|
||||
if not isinstance(wav_pr, torch.FloatTensor):
|
||||
wav_pr = torch.FloatTensor(wav_pr)
|
||||
if wav_pr.abs().max() > 1:
|
||||
wav_pr /= wav_pr.abs().max()
|
||||
if wav_pr.size(-1) == 2:
|
||||
wav_pr = wav_pr[:, 0]
|
||||
if wav_pr.ndim == 1:
|
||||
wav_pr = wav_pr.unsqueeze(0)
|
||||
assert wav_pr.ndim and wav_pr.size(0) == 1
|
||||
|
||||
if transcript_content == "":
|
||||
text_pr, lang_pr = make_prompt('dummy', wav_pr, sr, save=False)
|
||||
else:
|
||||
lang_pr = langid.classify(str(transcript_content))[0]
|
||||
lang_token = lang2token[lang_pr]
|
||||
text_pr = f"{lang_token}{str(transcript_content)}{lang_token}"
|
||||
|
||||
if language == 'auto-detect':
|
||||
lang_token = lang2token[langid.classify(text)[0]]
|
||||
else:
|
||||
lang_token = langdropdown2token[language]
|
||||
lang = token2lang[lang_token]
|
||||
text = lang_token + text + lang_token
|
||||
|
||||
# onload model
|
||||
model.to(device)
|
||||
|
||||
# tokenize audio
|
||||
encoded_frames = tokenize_audio(audio_tokenizer, (wav_pr, sr))
|
||||
audio_prompts = encoded_frames[0][0].transpose(2, 1).to(device)
|
||||
|
||||
# tokenize text
|
||||
logging.info(f"synthesize text: {text}")
|
||||
phone_tokens, langs = text_tokenizer.tokenize(text=f"_{text}".strip())
|
||||
text_tokens, text_tokens_lens = text_collater(
|
||||
[
|
||||
phone_tokens
|
||||
]
|
||||
)
|
||||
|
||||
enroll_x_lens = None
|
||||
if text_pr:
|
||||
text_prompts, _ = text_tokenizer.tokenize(text=f"{text_pr}".strip())
|
||||
text_prompts, enroll_x_lens = text_collater(
|
||||
[
|
||||
text_prompts
|
||||
]
|
||||
)
|
||||
text_tokens = torch.cat([text_prompts, text_tokens], dim=-1)
|
||||
text_tokens_lens += enroll_x_lens
|
||||
lang = lang if accent == "no-accent" else token2lang[langdropdown2token[accent]]
|
||||
encoded_frames = model.inference(
|
||||
text_tokens.to(device),
|
||||
text_tokens_lens.to(device),
|
||||
audio_prompts,
|
||||
enroll_x_lens=enroll_x_lens,
|
||||
top_k=-100,
|
||||
temperature=1,
|
||||
prompt_language=lang_pr,
|
||||
text_language=langs if accent == "no-accent" else lang,
|
||||
best_of=5,
|
||||
)
|
||||
# Decode with Vocos
|
||||
frames = encoded_frames.permute(2,0,1)
|
||||
features = vocos.codes_to_features(frames)
|
||||
samples = vocos.decode(features, bandwidth_id=torch.tensor([2], device=device))
|
||||
|
||||
# offload model
|
||||
model.to('cpu')
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
message = f"text prompt: {text_pr}\nsythesized text: {text}"
|
||||
return message, (24000, samples.squeeze(0).cpu().numpy())
|
||||
|
||||
@torch.no_grad()
|
||||
def infer_from_prompt(text, language, accent, preset_prompt, prompt_file):
|
||||
clear_prompts()
|
||||
model.to(device)
|
||||
# text to synthesize
|
||||
if language == 'auto-detect':
|
||||
lang_token = lang2token[langid.classify(text)[0]]
|
||||
else:
|
||||
lang_token = langdropdown2token[language]
|
||||
lang = token2lang[lang_token]
|
||||
text = lang_token + text + lang_token
|
||||
|
||||
# load prompt
|
||||
if prompt_file is not None:
|
||||
prompt_data = np.load(prompt_file.name)
|
||||
else:
|
||||
prompt_data = np.load(os.path.join("./presets/", f"{preset_prompt}.npz"))
|
||||
audio_prompts = prompt_data['audio_tokens']
|
||||
text_prompts = prompt_data['text_tokens']
|
||||
lang_pr = prompt_data['lang_code']
|
||||
lang_pr = code2lang[int(lang_pr)]
|
||||
|
||||
# numpy to tensor
|
||||
audio_prompts = torch.tensor(audio_prompts).type(torch.int32).to(device)
|
||||
text_prompts = torch.tensor(text_prompts).type(torch.int32)
|
||||
|
||||
enroll_x_lens = text_prompts.shape[-1]
|
||||
logging.info(f"synthesize text: {text}")
|
||||
phone_tokens, langs = text_tokenizer.tokenize(text=f"_{text}".strip())
|
||||
text_tokens, text_tokens_lens = text_collater(
|
||||
[
|
||||
phone_tokens
|
||||
]
|
||||
)
|
||||
text_tokens = torch.cat([text_prompts, text_tokens], dim=-1)
|
||||
text_tokens_lens += enroll_x_lens
|
||||
# accent control
|
||||
lang = lang if accent == "no-accent" else token2lang[langdropdown2token[accent]]
|
||||
encoded_frames = model.inference(
|
||||
text_tokens.to(device),
|
||||
text_tokens_lens.to(device),
|
||||
audio_prompts,
|
||||
enroll_x_lens=enroll_x_lens,
|
||||
top_k=-100,
|
||||
temperature=1,
|
||||
prompt_language=lang_pr,
|
||||
text_language=langs if accent == "no-accent" else lang,
|
||||
best_of=5,
|
||||
)
|
||||
# Decode with Vocos
|
||||
frames = encoded_frames.permute(2,0,1)
|
||||
features = vocos.codes_to_features(frames)
|
||||
samples = vocos.decode(features, bandwidth_id=torch.tensor([2], device=device))
|
||||
|
||||
model.to('cpu')
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
message = f"sythesized text: {text}"
|
||||
return message, (24000, samples.squeeze(0).cpu().numpy())
|
||||
|
||||
|
||||
from utils.sentence_cutter import split_text_into_sentences
|
||||
@torch.no_grad()
|
||||
def infer_long_text(text, preset_prompt, prompt=None, language='auto', accent='no-accent'):
|
||||
"""
|
||||
For long audio generation, two modes are available.
|
||||
fixed-prompt: This mode will keep using the same prompt the user has provided, and generate audio sentence by sentence.
|
||||
sliding-window: This mode will use the last sentence as the prompt for the next sentence, but has some concern on speaker maintenance.
|
||||
"""
|
||||
mode = 'fixed-prompt'
|
||||
global model, audio_tokenizer, text_tokenizer, text_collater
|
||||
model.to(device)
|
||||
if (prompt is None or prompt == "") and preset_prompt == "":
|
||||
mode = 'sliding-window' # If no prompt is given, use sliding-window mode
|
||||
sentences = split_text_into_sentences(text)
|
||||
# detect language
|
||||
if language == "auto-detect":
|
||||
language = langid.classify(text)[0]
|
||||
else:
|
||||
language = token2lang[langdropdown2token[language]]
|
||||
|
||||
# if initial prompt is given, encode it
|
||||
if prompt is not None and prompt != "":
|
||||
# load prompt
|
||||
prompt_data = np.load(prompt.name)
|
||||
audio_prompts = prompt_data['audio_tokens']
|
||||
text_prompts = prompt_data['text_tokens']
|
||||
lang_pr = prompt_data['lang_code']
|
||||
lang_pr = code2lang[int(lang_pr)]
|
||||
|
||||
# numpy to tensor
|
||||
audio_prompts = torch.tensor(audio_prompts).type(torch.int32).to(device)
|
||||
text_prompts = torch.tensor(text_prompts).type(torch.int32)
|
||||
elif preset_prompt is not None and preset_prompt != "":
|
||||
prompt_data = np.load(os.path.join("./presets/", f"{preset_prompt}.npz"))
|
||||
audio_prompts = prompt_data['audio_tokens']
|
||||
text_prompts = prompt_data['text_tokens']
|
||||
lang_pr = prompt_data['lang_code']
|
||||
lang_pr = code2lang[int(lang_pr)]
|
||||
|
||||
# numpy to tensor
|
||||
audio_prompts = torch.tensor(audio_prompts).type(torch.int32).to(device)
|
||||
text_prompts = torch.tensor(text_prompts).type(torch.int32)
|
||||
else:
|
||||
audio_prompts = torch.zeros([1, 0, NUM_QUANTIZERS]).type(torch.int32).to(device)
|
||||
text_prompts = torch.zeros([1, 0]).type(torch.int32)
|
||||
lang_pr = language if language != 'mix' else 'en'
|
||||
if mode == 'fixed-prompt':
|
||||
complete_tokens = torch.zeros([1, NUM_QUANTIZERS, 0]).type(torch.LongTensor).to(device)
|
||||
for text in sentences:
|
||||
text = text.replace("\n", "").strip(" ")
|
||||
if text == "":
|
||||
continue
|
||||
lang_token = lang2token[language]
|
||||
lang = token2lang[lang_token]
|
||||
text = lang_token + text + lang_token
|
||||
|
||||
enroll_x_lens = text_prompts.shape[-1]
|
||||
logging.info(f"synthesize text: {text}")
|
||||
phone_tokens, langs = text_tokenizer.tokenize(text=f"_{text}".strip())
|
||||
text_tokens, text_tokens_lens = text_collater(
|
||||
[
|
||||
phone_tokens
|
||||
]
|
||||
)
|
||||
text_tokens = torch.cat([text_prompts, text_tokens], dim=-1)
|
||||
text_tokens_lens += enroll_x_lens
|
||||
# accent control
|
||||
lang = lang if accent == "no-accent" else token2lang[langdropdown2token[accent]]
|
||||
encoded_frames = model.inference(
|
||||
text_tokens.to(device),
|
||||
text_tokens_lens.to(device),
|
||||
audio_prompts,
|
||||
enroll_x_lens=enroll_x_lens,
|
||||
top_k=-100,
|
||||
temperature=1,
|
||||
prompt_language=lang_pr,
|
||||
text_language=langs if accent == "no-accent" else lang,
|
||||
best_of=5,
|
||||
)
|
||||
complete_tokens = torch.cat([complete_tokens, encoded_frames.transpose(2, 1)], dim=-1)
|
||||
# Decode with Vocos
|
||||
frames = complete_tokens.permute(1, 0, 2)
|
||||
features = vocos.codes_to_features(frames)
|
||||
samples = vocos.decode(features, bandwidth_id=torch.tensor([2], device=device))
|
||||
|
||||
model.to('cpu')
|
||||
message = f"Cut into {len(sentences)} sentences"
|
||||
return message, (24000, samples.squeeze(0).cpu().numpy())
|
||||
elif mode == "sliding-window":
|
||||
complete_tokens = torch.zeros([1, NUM_QUANTIZERS, 0]).type(torch.LongTensor).to(device)
|
||||
original_audio_prompts = audio_prompts
|
||||
original_text_prompts = text_prompts
|
||||
for text in sentences:
|
||||
text = text.replace("\n", "").strip(" ")
|
||||
if text == "":
|
||||
continue
|
||||
lang_token = lang2token[language]
|
||||
lang = token2lang[lang_token]
|
||||
text = lang_token + text + lang_token
|
||||
|
||||
enroll_x_lens = text_prompts.shape[-1]
|
||||
logging.info(f"synthesize text: {text}")
|
||||
phone_tokens, langs = text_tokenizer.tokenize(text=f"_{text}".strip())
|
||||
text_tokens, text_tokens_lens = text_collater(
|
||||
[
|
||||
phone_tokens
|
||||
]
|
||||
)
|
||||
text_tokens = torch.cat([text_prompts, text_tokens], dim=-1)
|
||||
text_tokens_lens += enroll_x_lens
|
||||
# accent control
|
||||
lang = lang if accent == "no-accent" else token2lang[langdropdown2token[accent]]
|
||||
encoded_frames = model.inference(
|
||||
text_tokens.to(device),
|
||||
text_tokens_lens.to(device),
|
||||
audio_prompts,
|
||||
enroll_x_lens=enroll_x_lens,
|
||||
top_k=-100,
|
||||
temperature=1,
|
||||
prompt_language=lang_pr,
|
||||
text_language=langs if accent == "no-accent" else lang,
|
||||
best_of=5,
|
||||
)
|
||||
complete_tokens = torch.cat([complete_tokens, encoded_frames.transpose(2, 1)], dim=-1)
|
||||
if torch.rand(1) < 1.0:
|
||||
audio_prompts = encoded_frames[:, :, -NUM_QUANTIZERS:]
|
||||
text_prompts = text_tokens[:, enroll_x_lens:]
|
||||
else:
|
||||
audio_prompts = original_audio_prompts
|
||||
text_prompts = original_text_prompts
|
||||
# Decode with Vocos
|
||||
frames = complete_tokens.permute(1, 0, 2)
|
||||
features = vocos.codes_to_features(frames)
|
||||
samples = vocos.decode(features, bandwidth_id=torch.tensor([2], device=device))
|
||||
|
||||
model.to('cpu')
|
||||
message = f"Cut into {len(sentences)} sentences"
|
||||
return message, (24000, samples.squeeze(0).cpu().numpy())
|
||||
else:
|
||||
raise ValueError(f"No such mode {mode}")
|
||||
|
||||
|
||||
def main():
|
||||
app = gr.Blocks(title="VALL-E X")
|
||||
with app:
|
||||
gr.Markdown(top_md)
|
||||
with gr.Tab("Infer from audio"):
|
||||
gr.Markdown(infer_from_audio_md)
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
|
||||
textbox = gr.TextArea(label="Text",
|
||||
placeholder="Type your sentence here",
|
||||
value="Welcome back, Master. What can I do for you today?", elem_id=f"tts-input")
|
||||
language_dropdown = gr.Dropdown(choices=['auto-detect', 'English', '中文', '日本語'], value='auto-detect', label='language')
|
||||
accent_dropdown = gr.Dropdown(choices=['no-accent', 'English', '中文', '日本語'], value='no-accent', label='accent')
|
||||
textbox_transcript = gr.TextArea(label="Transcript",
|
||||
placeholder="Write transcript here. (leave empty to use whisper)",
|
||||
value="", elem_id=f"prompt-name")
|
||||
upload_audio_prompt = gr.Audio(label='uploaded audio prompt', source='upload', interactive=True)
|
||||
record_audio_prompt = gr.Audio(label='recorded audio prompt', source='microphone', interactive=True)
|
||||
with gr.Column():
|
||||
text_output = gr.Textbox(label="Message")
|
||||
audio_output = gr.Audio(label="Output Audio", elem_id="tts-audio")
|
||||
btn = gr.Button("Generate!")
|
||||
btn.click(infer_from_audio,
|
||||
inputs=[textbox, language_dropdown, accent_dropdown, upload_audio_prompt, record_audio_prompt, textbox_transcript],
|
||||
outputs=[text_output, audio_output])
|
||||
textbox_mp = gr.TextArea(label="Prompt name",
|
||||
placeholder="Name your prompt here",
|
||||
value="prompt_1", elem_id=f"prompt-name")
|
||||
btn_mp = gr.Button("Make prompt!")
|
||||
prompt_output = gr.File(interactive=False)
|
||||
btn_mp.click(make_npz_prompt,
|
||||
inputs=[textbox_mp, upload_audio_prompt, record_audio_prompt, textbox_transcript],
|
||||
outputs=[text_output, prompt_output])
|
||||
gr.Examples(examples=infer_from_audio_examples,
|
||||
inputs=[textbox, language_dropdown, accent_dropdown, upload_audio_prompt, record_audio_prompt, textbox_transcript],
|
||||
outputs=[text_output, audio_output],
|
||||
fn=infer_from_audio,
|
||||
cache_examples=False,)
|
||||
with gr.Tab("Make prompt"):
|
||||
gr.Markdown(make_prompt_md)
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
textbox2 = gr.TextArea(label="Prompt name",
|
||||
placeholder="Name your prompt here",
|
||||
value="prompt_1", elem_id=f"prompt-name")
|
||||
# 添加选择语言和输入台本的地方
|
||||
textbox_transcript2 = gr.TextArea(label="Transcript",
|
||||
placeholder="Write transcript here. (leave empty to use whisper)",
|
||||
value="", elem_id=f"prompt-name")
|
||||
upload_audio_prompt_2 = gr.Audio(label='uploaded audio prompt', source='upload', interactive=True)
|
||||
record_audio_prompt_2 = gr.Audio(label='recorded audio prompt', source='microphone', interactive=True)
|
||||
with gr.Column():
|
||||
text_output_2 = gr.Textbox(label="Message")
|
||||
prompt_output_2 = gr.File(interactive=False)
|
||||
btn_2 = gr.Button("Make!")
|
||||
btn_2.click(make_npz_prompt,
|
||||
inputs=[textbox2, upload_audio_prompt_2, record_audio_prompt_2, textbox_transcript2],
|
||||
outputs=[text_output_2, prompt_output_2])
|
||||
gr.Examples(examples=make_npz_prompt_examples,
|
||||
inputs=[textbox2, upload_audio_prompt_2, record_audio_prompt_2, textbox_transcript2],
|
||||
outputs=[text_output_2, prompt_output_2],
|
||||
fn=make_npz_prompt,
|
||||
cache_examples=False,)
|
||||
with gr.Tab("Infer from prompt"):
|
||||
gr.Markdown(infer_from_prompt_md)
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
textbox_3 = gr.TextArea(label="Text",
|
||||
placeholder="Type your sentence here",
|
||||
value="Welcome back, Master. What can I do for you today?", elem_id=f"tts-input")
|
||||
language_dropdown_3 = gr.Dropdown(choices=['auto-detect', 'English', '中文', '日本語', 'Mix'], value='auto-detect',
|
||||
label='language')
|
||||
accent_dropdown_3 = gr.Dropdown(choices=['no-accent', 'English', '中文', '日本語'], value='no-accent',
|
||||
label='accent')
|
||||
preset_dropdown_3 = gr.Dropdown(choices=preset_list, value=None, label='Voice preset')
|
||||
prompt_file = gr.File(file_count='single', file_types=['.npz'], interactive=True)
|
||||
with gr.Column():
|
||||
text_output_3 = gr.Textbox(label="Message")
|
||||
audio_output_3 = gr.Audio(label="Output Audio", elem_id="tts-audio")
|
||||
btn_3 = gr.Button("Generate!")
|
||||
btn_3.click(infer_from_prompt,
|
||||
inputs=[textbox_3, language_dropdown_3, accent_dropdown_3, preset_dropdown_3, prompt_file],
|
||||
outputs=[text_output_3, audio_output_3])
|
||||
gr.Examples(examples=infer_from_prompt_examples,
|
||||
inputs=[textbox_3, language_dropdown_3, accent_dropdown_3, preset_dropdown_3, prompt_file],
|
||||
outputs=[text_output_3, audio_output_3],
|
||||
fn=infer_from_prompt,
|
||||
cache_examples=False,)
|
||||
with gr.Tab("Infer long text"):
|
||||
gr.Markdown("This is a long text generation demo. You can use this to generate long audio. ")
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
textbox_4 = gr.TextArea(label="Text",
|
||||
placeholder="Type your sentence here",
|
||||
value=long_text_example, elem_id=f"tts-input")
|
||||
language_dropdown_4 = gr.Dropdown(choices=['auto-detect', 'English', '中文', '日本語'], value='auto-detect',
|
||||
label='language')
|
||||
accent_dropdown_4 = gr.Dropdown(choices=['no-accent', 'English', '中文', '日本語'], value='no-accent',
|
||||
label='accent')
|
||||
preset_dropdown_4 = gr.Dropdown(choices=preset_list, value=None, label='Voice preset')
|
||||
prompt_file_4 = gr.File(file_count='single', file_types=['.npz'], interactive=True)
|
||||
with gr.Column():
|
||||
text_output_4 = gr.TextArea(label="Message")
|
||||
audio_output_4 = gr.Audio(label="Output Audio", elem_id="tts-audio")
|
||||
btn_4 = gr.Button("Generate!")
|
||||
btn_4.click(infer_long_text,
|
||||
inputs=[textbox_4, preset_dropdown_4, prompt_file_4, language_dropdown_4, accent_dropdown_4],
|
||||
outputs=[text_output_4, audio_output_4])
|
||||
|
||||
webbrowser.open("http://127.0.0.1:7860")
|
||||
app.launch()
|
||||
|
||||
if __name__ == "__main__":
|
||||
formatter = (
|
||||
"%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||
)
|
||||
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||
main()
|
||||
@@ -0,0 +1,39 @@
|
||||
NUM_LAYERS = 12
|
||||
NUM_HEAD = 16
|
||||
N_DIM = 1024
|
||||
PREFIX_MODE = 1
|
||||
NUM_QUANTIZERS = 8
|
||||
SAMPLE_RATE = 24000
|
||||
|
||||
lang2token = {
|
||||
'zh': "[ZH]",
|
||||
'ja': "[JA]",
|
||||
"en": "[EN]",
|
||||
'mix': "",
|
||||
}
|
||||
|
||||
lang2code = {
|
||||
'zh': 0,
|
||||
'ja': 1,
|
||||
"en": 2,
|
||||
}
|
||||
|
||||
token2lang = {
|
||||
'[ZH]': "zh",
|
||||
'[JA]': "ja",
|
||||
"[EN]": "en",
|
||||
"": "mix"
|
||||
}
|
||||
|
||||
code2lang = {
|
||||
0: 'zh',
|
||||
1: 'ja',
|
||||
2: "en",
|
||||
}
|
||||
|
||||
langdropdown2token = {
|
||||
'English': "[EN]",
|
||||
'中文': "[ZH]",
|
||||
'日本語': "[JA]",
|
||||
'Mix': "",
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import argparse
|
||||
|
||||
# python3 main.py -create_prompt_with_list 20.wav 21.wav -create_prompt_name alice
|
||||
# python3 main.py -use_prompt_name alice -output_file_name alice_says.wav -text "This is the text to be converted to speech."
|
||||
|
||||
# python3 main.py -create_prompt_with_list 20.wav 21.wav 20.wav 21.wav -create_prompt_name alice -use_prompt_name alice -output_file_name alice_says.wav -text "This is my voice."
|
||||
|
||||
# python3 main.py -create_prompt_with_list goku.wav -create_prompt_name goku -use_prompt_name goku -output_file_name goku_test.wav -text "oh my god krillin! where did you put my sensu beans!?"
|
||||
|
||||
# python3 main.py -create_prompt_with_list
|
||||
|
||||
from typing import List
|
||||
import pathlib
|
||||
import whisperx
|
||||
|
||||
# https://discuss.pytorch.org/t/how-to-calculate-the-gpu-memory-that-a-model-uses/157486/6 <-- for fine tuning memory usage later.
|
||||
|
||||
from utils.prompt_making import make_prompt
|
||||
import logging
|
||||
|
||||
from utils.generation import SAMPLE_RATE, generate_audio, preload_models,load_models_from_mount
|
||||
import soundfile as sf
|
||||
import numpy as np
|
||||
import torchaudio
|
||||
from typing import List
|
||||
import uuid
|
||||
import torchaudio.transforms as T
|
||||
|
||||
class AudioOps():
|
||||
|
||||
@staticmethod
|
||||
def stitch_wav_tensors_with_crossfade(wav_tensor1:np.array,wav_tensor2:np.array,sample_rate:int):
|
||||
# Load two audio signals as NumPy tensors
|
||||
# Compute the length of the cross-fade window in samples
|
||||
fade_len = int(0.01 * sample_rate)
|
||||
|
||||
# Create a cross-fade window
|
||||
fade_window = np.hanning(2*fade_len)
|
||||
|
||||
# Concatenate the signals with cross-fade
|
||||
audio_out = np.concatenate((wav_tensor1[:-fade_len],
|
||||
wav_tensor1[-fade_len:] * fade_window[:fade_len] + wav_tensor2[:fade_len] * fade_window[fade_len:],
|
||||
wav_tensor2[fade_len:]))
|
||||
return audio_out
|
||||
|
||||
@staticmethod
|
||||
def cut_audio_range(file_path, start_seconds, end_seconds, output_path="cut_audio.wav"):
|
||||
"""
|
||||
Cuts out a specific segment from an audio file and saves the result.
|
||||
|
||||
Args:
|
||||
- file_path (str): Path to the audio file from which the segment will be cut.
|
||||
- start_seconds (float): Start time of the segment to be cut out in seconds.
|
||||
- end_seconds (float): End time of the segment to be cut out in seconds.
|
||||
- output_path (str): Path to save the resulting audio. Defaults to "cut_audio.wav".
|
||||
|
||||
Returns:
|
||||
- None
|
||||
"""
|
||||
# Load the audio file
|
||||
waveform, sr = torchaudio.load(file_path)
|
||||
# Convert the range to sample indices
|
||||
start_sample = int(start_seconds * sr)
|
||||
end_sample = int(end_seconds * sr)
|
||||
cut_tensor = waveform[:,start_sample:end_sample]
|
||||
torchaudio.save(output_path, cut_tensor, sr)
|
||||
|
||||
@staticmethod
|
||||
def stitch_wav_files_resample(root_path:pathlib.Path, files:List[str],target_sample_rate:int=SAMPLE_RATE):
|
||||
resampler = None
|
||||
result = None
|
||||
for file in files:
|
||||
if type(result) == type(None):
|
||||
tensor, sr = torchaudio.load(root_path / pathlib.Path(file))
|
||||
resampler = T.Resample(orig_freq=sr,new_freq=target_sample_rate)
|
||||
resampled_tensor = resampler(tensor)
|
||||
result = resampled_tensor.squeeze()
|
||||
result = np.array(result)
|
||||
else:
|
||||
tensor, _ = torchaudio.load(root_path / pathlib.Path(file))
|
||||
resampled_tensor = resampler(tensor)
|
||||
resampled_tensor = resampled_tensor.squeeze()
|
||||
resampled_tensor = np.array(resampled_tensor)
|
||||
result = AudioOps.stitch_wav_tensors_with_crossfade(result,resampled_tensor,sample_rate=target_sample_rate)
|
||||
|
||||
return result
|
||||
|
||||
class VoiceDesigner():
|
||||
def __init__(self) -> None:
|
||||
# load models from host system.
|
||||
self.logger = self.setup_logger()
|
||||
|
||||
self.mount_folder = pathlib.Path.cwd() / pathlib.Path("..") / pathlib.Path("Vall-E-mount")
|
||||
# Path for the inputs bunch of audio files
|
||||
self.input_path = self.mount_folder / pathlib.Path("input")
|
||||
# Path for the models to be used
|
||||
self.models_path = self.mount_folder / pathlib.Path("models")
|
||||
# Path for the existing presets
|
||||
self.presets_path = self.mount_folder / pathlib.Path("presets")
|
||||
# Path for the new prompts created by the user
|
||||
self.prompts_path = self.mount_folder / pathlib.Path("prompts")
|
||||
# Path for the temporary files
|
||||
self.temp_path = self.mount_folder / pathlib.Path("temp")
|
||||
# Path for output
|
||||
self.output_path = self.mount_folder / pathlib.Path("output")
|
||||
|
||||
load_models_from_mount(model_path=self.models_path,
|
||||
vocos_path=pathlib.Path("vocos-encodec-24khz"),
|
||||
vall_e_x=pathlib.Path("vallex-checkpoint.pt"))
|
||||
|
||||
def tts_with_prompt(self,prompt:pathlib.Path,text:str,output_file_name:str):
|
||||
# TODO Filtering bad words
|
||||
# TODO check if the files are actually wav ...
|
||||
audio_array = generate_audio(text,prompt=prompt)
|
||||
# save audio to disk
|
||||
sf.write(self.output_path / pathlib.Path(output_file_name), audio_array, SAMPLE_RATE)
|
||||
|
||||
def create_prompt(self,audio_file_paths:List[pathlib.Path],prompt_file_name:str):
|
||||
# multiple audio files concat
|
||||
# concat audio and trim the audio if it is longer than 14-15 seconds# if prompt is too long it will error out anyways
|
||||
if len(audio_file_paths) > 0:
|
||||
# TODO: going to assume brandon renames the files as they are uploaded
|
||||
try:# ./customs/ <-- folder it adds by default until we modified.
|
||||
# load the audio files and then concat them.
|
||||
tensor_result = AudioOps.stitch_wav_files_resample(root_path=self.input_path,files=audio_file_paths)
|
||||
# save to disk
|
||||
generated_name = f"{uuid.uuid4()}"
|
||||
|
||||
concat_audio_file_path = self.temp_path / pathlib.Path(f"audio_concat_{generated_name}.wav")
|
||||
sf.write(concat_audio_file_path, tensor_result, SAMPLE_RATE)
|
||||
# save result into processing...
|
||||
# trim to 15 seconds.
|
||||
audio_cut_path = self.temp_path / pathlib.Path(f"audio_cut_{generated_name}.wav")
|
||||
AudioOps.cut_audio_range(file_path=concat_audio_file_path,start_seconds=0,end_seconds=15,output_path=audio_cut_path)
|
||||
# TODO get whisper x to work in the container and generate transcription that way or use a different container to do that work.
|
||||
make_prompt(name=prompt_file_name,audio_prompt_output_path=self.prompts_path,audio_prompt_path=audio_cut_path,transcript=None)
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Exception: {e.with_traceback()}")
|
||||
raise
|
||||
else:
|
||||
self.logger.debug(f"No audio files to load")
|
||||
|
||||
def setup_logger(self):
|
||||
# Create a logger object
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Set the log level
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a file handler to log messages to a file
|
||||
file_handler = logging.FileHandler('docker_valle-x.log')
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a console handler to log messages to the console
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a formatter
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
# Set the formatter for the handlers
|
||||
file_handler.setFormatter(formatter)
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
# Add the handlers to the logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
global voice_designer
|
||||
voice_designer = VoiceDesigner()
|
||||
|
||||
def main(args):
|
||||
print(args.create_prompt_with_list)
|
||||
print(args.create_prompt_name)
|
||||
|
||||
print(args.text)
|
||||
print(args.use_prompt_name)
|
||||
print(args.output_file_name)
|
||||
|
||||
if args.create_prompt_name is not None and args.create_prompt_name is not None:
|
||||
voice_designer.create_prompt(audio_file_paths=args.create_prompt_with_list,
|
||||
prompt_file_name=args.create_prompt_name)
|
||||
|
||||
if args.text is not None and args.use_prompt_name is not None:
|
||||
voice_designer.tts_with_prompt(prompt=args.use_prompt_name,
|
||||
text=args.text,
|
||||
output_file_name=args.output_file_name)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description="Process a list of speaker samples for zero shot - .wav file pathes, convert text to speech, and specify language.")
|
||||
|
||||
parser.add_argument('-create_prompt_with_list', metavar='prompt wav files', nargs='+',
|
||||
help='Takes in a list of .wav files for an audio prompt provide the absolute path. Must be less than 15 seconds otherwise will error out.')
|
||||
|
||||
parser.add_argument('-create_prompt_name',metavar="prompt name to create",
|
||||
help='required with -create-prompt prompt output name this will be written to the mount. In Vall-E-mount/prompts')
|
||||
|
||||
parser.add_argument('-text', metavar='text to synthesize', type=str,
|
||||
help='Text to be converted to speech.')
|
||||
|
||||
parser.add_argument("-use_prompt_name",metavar="prompt you want to use",type=str)
|
||||
|
||||
parser.add_argument("-output_file_name",metavar="file name",type=str)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Model Card: VALL-E X
|
||||
|
||||
**Author**: [Songting](https://github.com/Plachtaa).<br>
|
||||
<br>
|
||||
This is the official codebase for running open-sourced VALL-E X.
|
||||
|
||||
The following is additional information about the models released here.
|
||||
|
||||
## Model Details
|
||||
|
||||
VALL-E X is a series of two transformer models that turn text into audio.
|
||||
|
||||
### Phoneme to acoustic tokens
|
||||
- Input: IPAs converted from input text by a rule-based G2P tool.
|
||||
- Output: tokens from the first codebook of the [EnCodec Codec](https://github.com/facebookresearch/encodec) from facebook
|
||||
|
||||
### Coarse to fine tokens
|
||||
- Input: IPAs converted from input text by a rule-based G2P tool & the first codebook from EnCodec
|
||||
- Output: 8 codebooks from EnCodec
|
||||
|
||||
### Architecture
|
||||
| Model | Parameters | Attention | Output Vocab size |
|
||||
|:------------------------:|:----------:|------------|:-----------------:|
|
||||
| G2P tool | - | - | 69 |
|
||||
| Phoneme to coarse tokens | 150 M | Causal | 1x 1,024 |
|
||||
| Coarse to fine tokens | 150 M | Non-causal | 7x 1,024 |
|
||||
|
||||
### Release date
|
||||
August 2023
|
||||
|
||||
## Broader Implications
|
||||
We anticipate that this model's text to audio capabilities can be used to improve accessbility tools in a variety of languages.
|
||||
Straightforward improvements will allow models to run faster than realtime, rendering them useful for applications such as virtual assistants.
|
||||
@@ -0,0 +1,136 @@
|
||||
import argparse
|
||||
|
||||
import torch.nn as nn
|
||||
# from icefall.utils import AttributeDict, str2bool
|
||||
|
||||
from .macros import (
|
||||
NUM_AUDIO_TOKENS,
|
||||
NUM_MEL_BINS,
|
||||
NUM_SPEAKER_CLASSES,
|
||||
NUM_TEXT_TOKENS,
|
||||
SPEAKER_EMBEDDING_DIM,
|
||||
)
|
||||
from .transformer import Transformer
|
||||
from .vallex import VALLE, VALLF
|
||||
from .visualizer import visualize
|
||||
|
||||
|
||||
def add_model_arguments(parser: argparse.ArgumentParser):
|
||||
parser.add_argument(
|
||||
"--model-name",
|
||||
type=str,
|
||||
default="VALL-E",
|
||||
help="VALL-E, VALL-F, Transformer.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--decoder-dim",
|
||||
type=int,
|
||||
default=1024,
|
||||
help="Embedding dimension in the decoder model.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nhead",
|
||||
type=int,
|
||||
default=16,
|
||||
help="Number of attention heads in the Decoder layers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-decoder-layers",
|
||||
type=int,
|
||||
default=12,
|
||||
help="Number of Decoder layers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--scale-factor",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Model scale factor which will be assigned different meanings in different models.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--norm-first",
|
||||
type=bool,
|
||||
default=True,
|
||||
help="Pre or Post Normalization.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--add-prenet",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Whether add PreNet after Inputs.",
|
||||
)
|
||||
|
||||
# VALL-E & F
|
||||
parser.add_argument(
|
||||
"--prefix-mode",
|
||||
type=int,
|
||||
default=1,
|
||||
help="The mode for how to prefix VALL-E NAR Decoder, "
|
||||
"0: no prefix, 1: 0 to random, 2: random to random, 4: chunk of pre or post utterance.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--share-embedding",
|
||||
type=bool,
|
||||
default=True,
|
||||
help="Share the parameters of the output projection layer with the parameters of the acoustic embedding.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prepend-bos",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Whether prepend <BOS> to the acoustic tokens -> AR Decoder inputs.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-quantizers",
|
||||
type=int,
|
||||
default=8,
|
||||
help="Number of Audio/Semantic quantization layers.",
|
||||
)
|
||||
|
||||
# Transformer
|
||||
parser.add_argument(
|
||||
"--scaling-xformers",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Apply Reworked Conformer scaling on Transformers.",
|
||||
)
|
||||
|
||||
|
||||
def get_model(params) -> nn.Module:
|
||||
if params.model_name.lower() in ["vall-f", "vallf"]:
|
||||
model = VALLF(
|
||||
params.decoder_dim,
|
||||
params.nhead,
|
||||
params.num_decoder_layers,
|
||||
norm_first=params.norm_first,
|
||||
add_prenet=params.add_prenet,
|
||||
prefix_mode=params.prefix_mode,
|
||||
share_embedding=params.share_embedding,
|
||||
nar_scale_factor=params.scale_factor,
|
||||
prepend_bos=params.prepend_bos,
|
||||
num_quantizers=params.num_quantizers,
|
||||
)
|
||||
elif params.model_name.lower() in ["vall-e", "valle"]:
|
||||
model = VALLE(
|
||||
params.decoder_dim,
|
||||
params.nhead,
|
||||
params.num_decoder_layers,
|
||||
norm_first=params.norm_first,
|
||||
add_prenet=params.add_prenet,
|
||||
prefix_mode=params.prefix_mode,
|
||||
share_embedding=params.share_embedding,
|
||||
nar_scale_factor=params.scale_factor,
|
||||
prepend_bos=params.prepend_bos,
|
||||
num_quantizers=params.num_quantizers,
|
||||
)
|
||||
else:
|
||||
assert params.model_name in ["Transformer"]
|
||||
model = Transformer(
|
||||
params.decoder_dim,
|
||||
params.nhead,
|
||||
params.num_decoder_layers,
|
||||
norm_first=params.norm_first,
|
||||
add_prenet=params.add_prenet,
|
||||
scaling_xformers=params.scaling_xformers,
|
||||
)
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,11 @@
|
||||
# Text
|
||||
NUM_TEXT_TOKENS = 2048
|
||||
|
||||
# Audio
|
||||
NUM_AUDIO_TOKENS = 1024 # EnCodec RVQ bins
|
||||
NUM_MEL_BINS = 100 # BigVGAN bigvgan_24khz_100band
|
||||
|
||||
|
||||
# Speaker
|
||||
NUM_SPEAKER_CLASSES = 4096
|
||||
SPEAKER_EMBEDDING_DIM = 64
|
||||
@@ -0,0 +1,394 @@
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from functools import partial
|
||||
from typing import Any, Dict, List, Tuple, Union
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
# from icefall.utils import make_pad_mask
|
||||
# from torchmetrics.classification import BinaryAccuracy
|
||||
|
||||
from models.vallex import Transpose
|
||||
from modules.embedding import SinePositionalEmbedding, TokenEmbedding
|
||||
from modules.scaling import BalancedDoubleSwish, ScaledLinear
|
||||
from modules.transformer import (
|
||||
BalancedBasicNorm,
|
||||
IdentityNorm,
|
||||
TransformerDecoderLayer,
|
||||
TransformerEncoder,
|
||||
TransformerEncoderLayer,
|
||||
)
|
||||
|
||||
from .macros import NUM_MEL_BINS, NUM_TEXT_TOKENS
|
||||
from .visualizer import visualize
|
||||
|
||||
IdentityNorm = IdentityNorm
|
||||
|
||||
|
||||
class Transformer(nn.Module):
|
||||
"""It implements seq2seq Transformer TTS for debug(No StopPredictor and SpeakerEmbeding)
|
||||
Neural Speech Synthesis with Transformer Network
|
||||
https://arxiv.org/abs/1809.08895
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
nhead: int,
|
||||
num_layers: int,
|
||||
norm_first: bool = True,
|
||||
add_prenet: bool = False,
|
||||
scaling_xformers: bool = False,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
d_model:
|
||||
The number of expected features in the input (required).
|
||||
nhead:
|
||||
The number of heads in the multiheadattention models (required).
|
||||
num_layers:
|
||||
The number of sub-decoder-layers in the decoder (required).
|
||||
"""
|
||||
super().__init__()
|
||||
self.text_embedding = TokenEmbedding(d_model, NUM_TEXT_TOKENS) # W_x
|
||||
|
||||
if add_prenet:
|
||||
self.encoder_prenet = nn.Sequential(
|
||||
Transpose(),
|
||||
nn.Conv1d(d_model, d_model, kernel_size=5, padding="same"),
|
||||
nn.BatchNorm1d(d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Conv1d(d_model, d_model, kernel_size=5, padding="same"),
|
||||
nn.BatchNorm1d(d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Conv1d(d_model, d_model, kernel_size=5, padding="same"),
|
||||
nn.BatchNorm1d(d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
Transpose(),
|
||||
nn.Linear(d_model, d_model),
|
||||
)
|
||||
|
||||
self.decoder_prenet = nn.Sequential(
|
||||
nn.Linear(NUM_MEL_BINS, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Linear(256, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Linear(256, d_model),
|
||||
)
|
||||
|
||||
assert scaling_xformers is False # TODO: update this block
|
||||
else:
|
||||
self.encoder_prenet = nn.Identity()
|
||||
if scaling_xformers:
|
||||
self.decoder_prenet = ScaledLinear(NUM_MEL_BINS, d_model)
|
||||
else:
|
||||
self.decoder_prenet = nn.Linear(NUM_MEL_BINS, d_model)
|
||||
|
||||
self.encoder_position = SinePositionalEmbedding(
|
||||
d_model,
|
||||
dropout=0.1,
|
||||
scale=False,
|
||||
)
|
||||
self.decoder_position = SinePositionalEmbedding(
|
||||
d_model, dropout=0.1, scale=False
|
||||
)
|
||||
|
||||
if scaling_xformers:
|
||||
self.encoder = TransformerEncoder(
|
||||
TransformerEncoderLayer(
|
||||
d_model,
|
||||
nhead,
|
||||
dim_feedforward=d_model * 4,
|
||||
dropout=0.1,
|
||||
batch_first=True,
|
||||
norm_first=norm_first,
|
||||
linear1_self_attention_cls=ScaledLinear,
|
||||
linear2_self_attention_cls=partial(
|
||||
ScaledLinear, initial_scale=0.01
|
||||
),
|
||||
linear1_feedforward_cls=ScaledLinear,
|
||||
linear2_feedforward_cls=partial(
|
||||
ScaledLinear, initial_scale=0.01
|
||||
),
|
||||
activation=partial(
|
||||
BalancedDoubleSwish,
|
||||
channel_dim=-1,
|
||||
max_abs=10.0,
|
||||
min_prob=0.25,
|
||||
),
|
||||
layer_norm_cls=IdentityNorm,
|
||||
),
|
||||
num_layers=num_layers,
|
||||
norm=BalancedBasicNorm(d_model) if norm_first else None,
|
||||
)
|
||||
|
||||
self.decoder = nn.TransformerDecoder(
|
||||
TransformerDecoderLayer(
|
||||
d_model,
|
||||
nhead,
|
||||
dim_feedforward=d_model * 4,
|
||||
dropout=0.1,
|
||||
batch_first=True,
|
||||
norm_first=norm_first,
|
||||
linear1_self_attention_cls=ScaledLinear,
|
||||
linear2_self_attention_cls=partial(
|
||||
ScaledLinear, initial_scale=0.01
|
||||
),
|
||||
linear1_feedforward_cls=ScaledLinear,
|
||||
linear2_feedforward_cls=partial(
|
||||
ScaledLinear, initial_scale=0.01
|
||||
),
|
||||
activation=partial(
|
||||
BalancedDoubleSwish,
|
||||
channel_dim=-1,
|
||||
max_abs=10.0,
|
||||
min_prob=0.25,
|
||||
),
|
||||
layer_norm_cls=IdentityNorm,
|
||||
),
|
||||
num_layers=num_layers,
|
||||
norm=BalancedBasicNorm(d_model) if norm_first else None,
|
||||
)
|
||||
|
||||
self.predict_layer = ScaledLinear(d_model, NUM_MEL_BINS)
|
||||
self.stop_layer = nn.Linear(d_model, 1)
|
||||
else:
|
||||
self.encoder = nn.TransformerEncoder(
|
||||
nn.TransformerEncoderLayer(
|
||||
d_model,
|
||||
nhead,
|
||||
dim_feedforward=d_model * 4,
|
||||
activation=F.relu,
|
||||
dropout=0.1,
|
||||
batch_first=True,
|
||||
norm_first=norm_first,
|
||||
),
|
||||
num_layers=num_layers,
|
||||
norm=nn.LayerNorm(d_model) if norm_first else None,
|
||||
)
|
||||
|
||||
self.decoder = nn.TransformerDecoder(
|
||||
nn.TransformerDecoderLayer(
|
||||
d_model,
|
||||
nhead,
|
||||
dim_feedforward=d_model * 4,
|
||||
activation=F.relu,
|
||||
dropout=0.1,
|
||||
batch_first=True,
|
||||
norm_first=norm_first,
|
||||
),
|
||||
num_layers=num_layers,
|
||||
norm=nn.LayerNorm(d_model) if norm_first else None,
|
||||
)
|
||||
|
||||
self.predict_layer = nn.Linear(d_model, NUM_MEL_BINS)
|
||||
self.stop_layer = nn.Linear(d_model, 1)
|
||||
|
||||
self.stop_accuracy_metric = BinaryAccuracy(
|
||||
threshold=0.5, multidim_average="global"
|
||||
)
|
||||
|
||||
# self.apply(self._init_weights)
|
||||
|
||||
# def _init_weights(self, module):
|
||||
# if isinstance(module, (nn.Linear)):
|
||||
# module.weight.data.normal_(mean=0.0, std=0.02)
|
||||
# if isinstance(module, nn.Linear) and module.bias is not None:
|
||||
# module.bias.data.zero_()
|
||||
# elif isinstance(module, nn.LayerNorm):
|
||||
# module.bias.data.zero_()
|
||||
# module.weight.data.fill_(1.0)
|
||||
# elif isinstance(module, nn.Embedding):
|
||||
# module.weight.data.normal_(mean=0.0, std=0.02)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: torch.Tensor,
|
||||
y_lens: torch.Tensor,
|
||||
reduction: str = "sum",
|
||||
train_stage: int = 0,
|
||||
**kwargs,
|
||||
) -> Tuple[torch.Tensor, Union[torch.Tensor, None]]:
|
||||
"""
|
||||
Args:
|
||||
x:
|
||||
A 2-D tensor of shape (N, S).
|
||||
x_lens:
|
||||
A 1-D tensor of shape (N,). It contains the number of tokens in `x`
|
||||
before padding.
|
||||
y:
|
||||
A 3-D tensor of shape (N, T, 8).
|
||||
y_lens:
|
||||
A 1-D tensor of shape (N,). It contains the number of tokens in `x`
|
||||
before padding.
|
||||
train_stage:
|
||||
Not used in this model.
|
||||
Returns:
|
||||
Return the predicted audio code matrix, cross-entropy loss and Top-10 accuracy.
|
||||
"""
|
||||
del train_stage
|
||||
|
||||
assert x.ndim == 2, x.shape
|
||||
assert x_lens.ndim == 1, x_lens.shape
|
||||
assert y.ndim == 3, y.shape
|
||||
assert y_lens.ndim == 1, y_lens.shape
|
||||
|
||||
assert torch.all(x_lens > 0)
|
||||
|
||||
# NOTE: x has been padded in TextTokenCollater
|
||||
x_mask = make_pad_mask(x_lens).to(x.device)
|
||||
|
||||
x = self.text_embedding(x)
|
||||
x = self.encoder_prenet(x)
|
||||
x = self.encoder_position(x)
|
||||
x = self.encoder(x, src_key_padding_mask=x_mask)
|
||||
|
||||
total_loss, metrics = 0.0, {}
|
||||
|
||||
y_mask = make_pad_mask(y_lens).to(y.device)
|
||||
y_mask_float = y_mask.type(torch.float32)
|
||||
data_mask = 1.0 - y_mask_float.unsqueeze(-1)
|
||||
|
||||
# Training
|
||||
# AR Decoder
|
||||
def pad_y(y):
|
||||
y = F.pad(y, (0, 0, 1, 0, 0, 0), value=0).detach()
|
||||
# inputs, targets
|
||||
return y[:, :-1], y[:, 1:]
|
||||
|
||||
y, targets = pad_y(y * data_mask) # mask padding as zeros
|
||||
|
||||
y_emb = self.decoder_prenet(y)
|
||||
y_pos = self.decoder_position(y_emb)
|
||||
|
||||
y_len = y_lens.max()
|
||||
tgt_mask = torch.triu(
|
||||
torch.ones(y_len, y_len, device=y.device, dtype=torch.bool),
|
||||
diagonal=1,
|
||||
)
|
||||
y_dec = self.decoder(
|
||||
y_pos,
|
||||
x,
|
||||
tgt_mask=tgt_mask,
|
||||
memory_key_padding_mask=x_mask,
|
||||
)
|
||||
|
||||
predict = self.predict_layer(y_dec)
|
||||
# loss
|
||||
total_loss = F.mse_loss(predict, targets, reduction=reduction)
|
||||
|
||||
logits = self.stop_layer(y_dec).squeeze(-1)
|
||||
stop_loss = F.binary_cross_entropy_with_logits(
|
||||
logits,
|
||||
y_mask_float.detach(),
|
||||
weight=1.0 + y_mask_float.detach() * 4.0,
|
||||
reduction=reduction,
|
||||
)
|
||||
metrics["stop_loss"] = stop_loss.detach()
|
||||
|
||||
stop_accuracy = self.stop_accuracy_metric(
|
||||
(torch.sigmoid(logits) >= 0.5).type(torch.int64),
|
||||
y_mask.type(torch.int64),
|
||||
)
|
||||
# icefall MetricsTracker.norm_items()
|
||||
metrics["stop_accuracy"] = stop_accuracy.item() * y_lens.sum().type(
|
||||
torch.float32
|
||||
)
|
||||
|
||||
return ((x, predict), total_loss + 100.0 * stop_loss, metrics)
|
||||
|
||||
def inference(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: Any = None,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Args:
|
||||
x:
|
||||
A 2-D tensor of shape (1, S).
|
||||
x_lens:
|
||||
A 1-D tensor of shape (1,). It contains the number of tokens in `x`
|
||||
before padding.
|
||||
Returns:
|
||||
Return the predicted audio code matrix and cross-entropy loss.
|
||||
"""
|
||||
assert x.ndim == 2, x.shape
|
||||
assert x_lens.ndim == 1, x_lens.shape
|
||||
|
||||
assert torch.all(x_lens > 0)
|
||||
|
||||
x_mask = make_pad_mask(x_lens).to(x.device)
|
||||
|
||||
x = self.text_embedding(x)
|
||||
x = self.encoder_prenet(x)
|
||||
x = self.encoder_position(x)
|
||||
x = self.encoder(x, src_key_padding_mask=x_mask)
|
||||
|
||||
x_mask = make_pad_mask(x_lens).to(x.device)
|
||||
|
||||
# AR Decoder
|
||||
# TODO: Managing decoder steps avoid repetitive computation
|
||||
y = torch.zeros(
|
||||
[x.shape[0], 1, NUM_MEL_BINS], dtype=torch.float32, device=x.device
|
||||
)
|
||||
while True:
|
||||
y_emb = self.decoder_prenet(y)
|
||||
y_pos = self.decoder_position(y_emb)
|
||||
|
||||
tgt_mask = torch.triu(
|
||||
torch.ones(
|
||||
y.shape[1], y.shape[1], device=y.device, dtype=torch.bool
|
||||
),
|
||||
diagonal=1,
|
||||
)
|
||||
|
||||
y_dec = self.decoder(
|
||||
y_pos,
|
||||
x,
|
||||
tgt_mask=tgt_mask,
|
||||
memory_mask=None,
|
||||
memory_key_padding_mask=x_mask,
|
||||
)
|
||||
predict = self.predict_layer(y_dec[:, -1:])
|
||||
|
||||
logits = self.stop_layer(y_dec[:, -1:]) > 0 # sigmoid(0.0) = 0.5
|
||||
if y.shape[1] > x_lens.max() * 10 or all(logits.cpu().numpy()):
|
||||
print(
|
||||
f"TransformerTTS EOS [Text {x_lens[0]} -> Audio {y.shape[1]}]"
|
||||
)
|
||||
break
|
||||
|
||||
y = torch.concat([y, predict], dim=1)
|
||||
|
||||
return y[:, 1:]
|
||||
|
||||
def visualize(
|
||||
self,
|
||||
predicts: Tuple[torch.Tensor],
|
||||
batch: Dict[str, Union[List, torch.Tensor]],
|
||||
output_dir: str,
|
||||
limit: int = 4,
|
||||
) -> None:
|
||||
visualize(predicts, batch, output_dir, limit=limit)
|
||||
@@ -0,0 +1,853 @@
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import random
|
||||
from typing import Dict, Iterator, List, Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
# from icefall.utils import make_pad_mask
|
||||
# from torchmetrics.classification import MulticlassAccuracy
|
||||
|
||||
from data.input_strategies import PromptedFeatures
|
||||
from modules.embedding import SinePositionalEmbedding, TokenEmbedding
|
||||
from modules.transformer import (
|
||||
AdaptiveLayerNorm,
|
||||
LayerNorm,
|
||||
TransformerDecoderLayer,
|
||||
TransformerEncoder,
|
||||
TransformerEncoderLayer,
|
||||
)
|
||||
|
||||
from .macros import NUM_AUDIO_TOKENS, NUM_TEXT_TOKENS
|
||||
from .visualizer import visualize
|
||||
|
||||
|
||||
class Transpose(nn.Identity):
|
||||
"""(N, T, D) -> (N, D, T)"""
|
||||
|
||||
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
||||
return input.transpose(1, 2)
|
||||
|
||||
|
||||
# NOTE: There are two ways to implement the model
|
||||
# 1) [VALL-F] standard TransformerDecoder, use x as memory
|
||||
# 2) [VALL-E] modified TransformerDecoder like GPT-x(e.g. causal TransformerEncoder),
|
||||
# use x as the prefix of decoder inputs
|
||||
class VALLF(nn.Module):
|
||||
"""It implements https://arxiv.org/abs/2301.02111
|
||||
"Neural Codec Language Models are Zero-Shot Text to Speech Synthesizers"
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
nhead: int,
|
||||
num_layers: int,
|
||||
norm_first: bool = True,
|
||||
add_prenet: bool = False,
|
||||
decoder_cls: Union[
|
||||
nn.TransformerDecoder, nn.TransformerEncoder
|
||||
] = nn.TransformerDecoder,
|
||||
decoder_layer_cls: Union[
|
||||
TransformerDecoderLayer, TransformerEncoderLayer
|
||||
] = TransformerDecoderLayer,
|
||||
prefix_mode: int = 0,
|
||||
share_embedding: bool = True,
|
||||
nar_scale_factor: float = 1.0,
|
||||
prepend_bos: bool = True,
|
||||
num_quantizers: int = 8,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
d_model:
|
||||
The number of expected features in the input (required).
|
||||
nhead:
|
||||
The number of heads in the multiheadattention models (required).
|
||||
num_layers:
|
||||
The number of sub-decoder-layers in the decoder (required).
|
||||
"""
|
||||
super().__init__()
|
||||
nar_d_model = int(d_model * nar_scale_factor)
|
||||
|
||||
self.ar_text_embedding = TokenEmbedding(d_model, NUM_TEXT_TOKENS) # W_x
|
||||
self.nar_text_embedding = TokenEmbedding(nar_d_model, NUM_TEXT_TOKENS)
|
||||
|
||||
# ID NUM_AUDIO_TOKENS -> PAD
|
||||
# ID NUM_AUDIO_TOKENS + 1 -> BOS
|
||||
self.ar_audio_prepend_bos = prepend_bos
|
||||
self.ar_audio_embedding = TokenEmbedding(
|
||||
d_model, NUM_AUDIO_TOKENS + 1 + int(prepend_bos)
|
||||
)
|
||||
|
||||
# PreNet
|
||||
if add_prenet:
|
||||
self.ar_text_prenet = nn.Sequential(
|
||||
Transpose(),
|
||||
nn.Conv1d(d_model, d_model, kernel_size=5, padding="same"),
|
||||
nn.BatchNorm1d(d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Conv1d(d_model, d_model, kernel_size=5, padding="same"),
|
||||
nn.BatchNorm1d(d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Conv1d(d_model, d_model, kernel_size=5, padding="same"),
|
||||
nn.BatchNorm1d(d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
Transpose(),
|
||||
nn.Linear(d_model, d_model),
|
||||
)
|
||||
|
||||
self.ar_audio_prenet = nn.Sequential(
|
||||
nn.Linear(d_model, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.25),
|
||||
nn.Linear(256, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.25),
|
||||
nn.Linear(256, d_model),
|
||||
)
|
||||
else:
|
||||
self.ar_text_prenet = nn.Identity()
|
||||
self.ar_audio_prenet = nn.Identity()
|
||||
|
||||
self.ar_text_position = SinePositionalEmbedding(
|
||||
d_model,
|
||||
dropout=0.1,
|
||||
scale=False,
|
||||
alpha=True,
|
||||
)
|
||||
self.ar_audio_position = SinePositionalEmbedding(
|
||||
d_model,
|
||||
dropout=0.1,
|
||||
scale=False,
|
||||
alpha=True,
|
||||
)
|
||||
|
||||
self.ar_decoder = decoder_cls(
|
||||
decoder_layer_cls(
|
||||
d_model,
|
||||
nhead,
|
||||
dim_feedforward=d_model * 4,
|
||||
dropout=0.1,
|
||||
batch_first=True,
|
||||
norm_first=norm_first,
|
||||
),
|
||||
num_layers=num_layers,
|
||||
norm=LayerNorm(d_model) if norm_first else None,
|
||||
)
|
||||
self.ar_predict_layer = nn.Linear(
|
||||
d_model, NUM_AUDIO_TOKENS + 1, bias=False
|
||||
)
|
||||
|
||||
self.rng = random.Random(0)
|
||||
self.num_heads = nhead
|
||||
self.prefix_mode = prefix_mode
|
||||
self.num_quantizers = num_quantizers
|
||||
|
||||
assert num_quantizers >= 1
|
||||
if num_quantizers > 1:
|
||||
self.nar_audio_embeddings = nn.ModuleList(
|
||||
[TokenEmbedding(nar_d_model, NUM_AUDIO_TOKENS + 1)]
|
||||
+ [
|
||||
TokenEmbedding(nar_d_model, NUM_AUDIO_TOKENS)
|
||||
for i in range(num_quantizers - 1)
|
||||
]
|
||||
) # W_a
|
||||
|
||||
# PreNet
|
||||
if add_prenet:
|
||||
self.nar_text_prenet = nn.Sequential(
|
||||
Transpose(),
|
||||
nn.Conv1d(
|
||||
nar_d_model, nar_d_model, kernel_size=5, padding="same"
|
||||
),
|
||||
nn.BatchNorm1d(nar_d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Conv1d(
|
||||
nar_d_model, nar_d_model, kernel_size=5, padding="same"
|
||||
),
|
||||
nn.BatchNorm1d(nar_d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
nn.Conv1d(
|
||||
nar_d_model, nar_d_model, kernel_size=5, padding="same"
|
||||
),
|
||||
nn.BatchNorm1d(nar_d_model),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.5),
|
||||
Transpose(),
|
||||
nn.Linear(nar_d_model, nar_d_model),
|
||||
)
|
||||
self.nar_audio_prenet = nn.Sequential(
|
||||
nn.Linear(nar_d_model, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.25),
|
||||
nn.Linear(256, 256),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(0.25),
|
||||
nn.Linear(256, nar_d_model),
|
||||
)
|
||||
else:
|
||||
self.nar_text_prenet = nn.Identity()
|
||||
self.nar_audio_prenet = nn.Identity()
|
||||
|
||||
self.nar_text_position = SinePositionalEmbedding(
|
||||
nar_d_model,
|
||||
dropout=0.0,
|
||||
scale=False,
|
||||
alpha=False,
|
||||
)
|
||||
self.nar_audio_position = SinePositionalEmbedding(
|
||||
nar_d_model,
|
||||
dropout=0.1,
|
||||
scale=False,
|
||||
alpha=False,
|
||||
)
|
||||
|
||||
self.nar_decoder = decoder_cls(
|
||||
decoder_layer_cls(
|
||||
nar_d_model,
|
||||
int(nhead * nar_scale_factor),
|
||||
dim_feedforward=nar_d_model * 4,
|
||||
dropout=0.1,
|
||||
batch_first=True,
|
||||
norm_first=norm_first,
|
||||
adaptive_layer_norm=True,
|
||||
),
|
||||
num_layers=int(num_layers * nar_scale_factor),
|
||||
norm=AdaptiveLayerNorm(
|
||||
nar_d_model, norm=nn.LayerNorm(nar_d_model)
|
||||
)
|
||||
if norm_first
|
||||
else None,
|
||||
)
|
||||
self.nar_predict_layers = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(nar_d_model, NUM_AUDIO_TOKENS, bias=False)
|
||||
for i in range(num_quantizers - 1)
|
||||
]
|
||||
)
|
||||
self.nar_stage_embeddings = nn.ModuleList(
|
||||
[
|
||||
TokenEmbedding(nar_d_model, 1)
|
||||
for i in range(num_quantizers - 1)
|
||||
]
|
||||
)
|
||||
|
||||
if share_embedding:
|
||||
# We share the parameters of the output projection layer with the parameters of the acoustic embedding Wa
|
||||
# NOTE(Feiteng): In the experiment, this undermines accuracy
|
||||
# self.ar_predict_layer.weight = self.ar_audio_embedding.weight
|
||||
|
||||
# We also share the parameters of the acoustic embedding layer and the output prediction layer,
|
||||
# which means the weights of the j-th prediction layer are the same as the (j + 1)-th acoustic embedding layer.
|
||||
for j in range(0, num_quantizers - 2):
|
||||
self.nar_predict_layers[
|
||||
j
|
||||
].weight = self.nar_audio_embeddings[j + 2].weight
|
||||
|
||||
def stage_parameters(self, stage: int = 1) -> Iterator[nn.Parameter]:
|
||||
assert stage > 0
|
||||
if stage == 1:
|
||||
for name, param in self.named_parameters():
|
||||
if name.startswith("ar_"):
|
||||
print(f" AR parameter: {name}")
|
||||
yield param
|
||||
|
||||
if stage == 2:
|
||||
for name, param in self.named_parameters():
|
||||
if name.startswith("nar_"):
|
||||
print(f"NAR parameter: {name}")
|
||||
yield param
|
||||
|
||||
def stage_named_parameters(
|
||||
self, stage: int = 1
|
||||
) -> Iterator[Tuple[str, nn.Parameter]]:
|
||||
assert stage > 0
|
||||
if stage == 1:
|
||||
for pair in self.named_parameters():
|
||||
if pair[0].startswith("ar_"):
|
||||
yield pair
|
||||
|
||||
if stage == 2:
|
||||
for pair in self.named_parameters():
|
||||
if pair[0].startswith("nar_"):
|
||||
yield pair
|
||||
|
||||
def pad_y_eos(self, y, y_mask_int, eos_id):
|
||||
targets = F.pad(y, (0, 1), value=0) + eos_id * F.pad(
|
||||
y_mask_int, (0, 1), value=1
|
||||
)
|
||||
# inputs, targets
|
||||
if self.ar_audio_prepend_bos:
|
||||
return (
|
||||
F.pad(targets[:, :-1], (1, 0), value=NUM_AUDIO_TOKENS + 1),
|
||||
targets,
|
||||
)
|
||||
|
||||
return targets[:, :-1], targets[:, 1:]
|
||||
|
||||
def _prepare_prompts(self, y, y_lens, codes, nar_stage, y_prompts_codes, prefix_mode):
|
||||
# 5.1 For the NAR acoustic prompt tokens, we select a random segment waveform of 3 seconds
|
||||
# from the same utterance.
|
||||
# We implement this differently.
|
||||
if prefix_mode == 0:
|
||||
# no prefix
|
||||
prefix_len = 0
|
||||
y_emb = self.nar_audio_embeddings[0](y)
|
||||
for j in range(1, nar_stage):
|
||||
# Formula (4) (5)
|
||||
y_emb = y_emb + self.nar_audio_embeddings[j](codes[..., j])
|
||||
elif prefix_mode == 1:
|
||||
# prefix at begining
|
||||
int_low = (0.25 * y_lens.min()).type(torch.int64).item()
|
||||
prefix_len = torch.randint(0, int_low * 2, size=()).item()
|
||||
prefix_len = min(prefix_len, 225) # 24000/320 * 3s = 225 frames
|
||||
|
||||
y_prompts = self.nar_audio_embeddings[0](y[:, :prefix_len])
|
||||
y_emb = self.nar_audio_embeddings[0](y[:, prefix_len:])
|
||||
for j in range(1, self.num_quantizers):
|
||||
y_prompts += self.nar_audio_embeddings[j](
|
||||
codes[:, :prefix_len, j]
|
||||
)
|
||||
if j < nar_stage:
|
||||
y_emb += self.nar_audio_embeddings[j](
|
||||
codes[:, prefix_len:, j]
|
||||
)
|
||||
y_emb = torch.concat([y_prompts, y_emb], axis=1)
|
||||
elif prefix_mode in [2, 4]:
|
||||
if prefix_mode == 2:
|
||||
# random prefix
|
||||
prefix_len = min(225, int(0.25 * y_lens.min().item()))
|
||||
|
||||
y_prompts_codes = []
|
||||
for b in range(codes.shape[0]):
|
||||
start = self.rng.randint(0, y_lens[b].item() - prefix_len)
|
||||
y_prompts_codes.append(
|
||||
torch.clone(codes[b, start : start + prefix_len])
|
||||
)
|
||||
codes[
|
||||
b, start : start + prefix_len, nar_stage
|
||||
] = NUM_AUDIO_TOKENS
|
||||
y_prompts_codes = torch.stack(y_prompts_codes, dim=0)
|
||||
else:
|
||||
prefix_len = y_prompts_codes.shape[1]
|
||||
|
||||
y_prompts = self.nar_audio_embeddings[0](y_prompts_codes[..., 0])
|
||||
y_emb = self.nar_audio_embeddings[0](y)
|
||||
for j in range(1, self.num_quantizers):
|
||||
y_prompts += self.nar_audio_embeddings[j](
|
||||
y_prompts_codes[..., j]
|
||||
)
|
||||
if j < nar_stage:
|
||||
y_emb += self.nar_audio_embeddings[j](codes[..., j])
|
||||
y_emb = torch.concat([y_prompts, y_emb], axis=1)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
return y_emb, prefix_len
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: Union[torch.Tensor, PromptedFeatures],
|
||||
y_lens: Union[torch.Tensor, PromptedFeatures],
|
||||
reduction: str = "sum",
|
||||
train_stage: int = 0,
|
||||
**kwargs,
|
||||
) -> Tuple[torch.Tensor, Union[torch.Tensor, None]]:
|
||||
raise NotImplementedError
|
||||
|
||||
def inference(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: torch.Tensor,
|
||||
enroll_x_lens: Union[torch.Tensor, None] = None,
|
||||
top_k: int = -100,
|
||||
temperature: float = 1.0,
|
||||
) -> torch.Tensor:
|
||||
raise NotImplementedError
|
||||
|
||||
def visualize(
|
||||
self,
|
||||
predicts: Tuple[torch.Tensor],
|
||||
batch: Dict[str, Union[List, torch.Tensor]],
|
||||
output_dir: str,
|
||||
limit: int = 4,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class VALLE(VALLF):
|
||||
"""It implements https://arxiv.org/abs/2301.02111
|
||||
"Neural Codec Language Models are Zero-Shot Text to Speech Synthesizers"
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
nhead: int,
|
||||
num_layers: int,
|
||||
norm_first: bool = True,
|
||||
add_prenet: bool = False,
|
||||
prefix_mode: int = 0,
|
||||
share_embedding: bool = True,
|
||||
nar_scale_factor: float = 1.0,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
d_model:
|
||||
The number of expected features in the input (required).
|
||||
nhead:
|
||||
The number of heads in the multiheadattention models (required).
|
||||
num_layers:
|
||||
The number of sub-decoder-layers in the decoder (required).
|
||||
"""
|
||||
super(VALLE, self).__init__(
|
||||
d_model,
|
||||
nhead,
|
||||
num_layers,
|
||||
norm_first=norm_first,
|
||||
add_prenet=add_prenet,
|
||||
decoder_cls=TransformerEncoder,
|
||||
decoder_layer_cls=TransformerEncoderLayer,
|
||||
prefix_mode=prefix_mode,
|
||||
share_embedding=share_embedding,
|
||||
nar_scale_factor=nar_scale_factor,
|
||||
**kwargs,
|
||||
)
|
||||
self.language_ID = {
|
||||
'en': 0,
|
||||
'zh': 1,
|
||||
'ja': 2,
|
||||
}
|
||||
self.ar_language_embedding = TokenEmbedding(d_model, len(self.language_ID))
|
||||
self.nar_language_embedding = TokenEmbedding(d_model, len(self.language_ID))
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: Union[torch.Tensor, PromptedFeatures],
|
||||
y_lens: Union[torch.Tensor, PromptedFeatures],
|
||||
reduction: str = "sum",
|
||||
train_stage: int = 0,
|
||||
**kwargs,
|
||||
):
|
||||
raise NotImplementedError
|
||||
def inference(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: torch.Tensor,
|
||||
enroll_x_lens: torch.Tensor,
|
||||
top_k: int = -100,
|
||||
temperature: float = 1.0,
|
||||
prompt_language: str = None,
|
||||
text_language: str = None,
|
||||
best_of: int = 1,
|
||||
length_penalty: float = 1.0,
|
||||
return_worst: bool = False,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Args:
|
||||
x:
|
||||
A 2-D tensor of shape (1, S).
|
||||
x_lens:
|
||||
A 1-D tensor of shape (1,). It contains the number of tokens in `x`
|
||||
before padding.
|
||||
y:
|
||||
A 3-D tensor of shape (1, T, 8).
|
||||
top_k: (`optional`) int
|
||||
The number of highest probability tokens to keep for top-k-filtering. Default to -100.
|
||||
temperature: (`optional`) float
|
||||
The value used to module the next token probabilities. Must be strictly positive. Default to 1.0.
|
||||
Returns:
|
||||
Return the predicted audio code matrix.
|
||||
"""
|
||||
assert x.ndim == 2, x.shape
|
||||
assert x_lens.ndim == 1, x_lens.shape
|
||||
assert y.ndim == 3, y.shape
|
||||
assert y.shape[0] == 1, y.shape
|
||||
|
||||
assert torch.all(x_lens > 0)
|
||||
|
||||
# NOTE: x has been padded in TextTokenCollater
|
||||
text = x
|
||||
x = self.ar_text_embedding(text)
|
||||
# Add language embedding
|
||||
prompt_language_id = torch.LongTensor(np.array([self.language_ID[prompt_language]])).to(x.device)
|
||||
if isinstance(text_language, str):
|
||||
text_language_id = torch.LongTensor(np.array([self.language_ID[text_language]])).to(x.device)
|
||||
elif isinstance(text_language, List):
|
||||
text_language_id = torch.LongTensor(np.array([self.language_ID[tl] for tl in text_language])).to(x.device)
|
||||
x[:, :enroll_x_lens, :] += self.ar_language_embedding(prompt_language_id)
|
||||
x[:, enroll_x_lens:, :] += self.ar_language_embedding(text_language_id)
|
||||
x = self.ar_text_prenet(x)
|
||||
x = self.ar_text_position(x)
|
||||
|
||||
text_len = x_lens.max()
|
||||
prompts = y
|
||||
prefix_len = y.shape[1]
|
||||
|
||||
# AR Decoder
|
||||
# TODO: Managing decoder steps avoid repetitive computation
|
||||
y = prompts[..., 0]
|
||||
if self.ar_audio_prepend_bos:
|
||||
y = F.pad(y, (1, 0), value=NUM_AUDIO_TOKENS + 1)
|
||||
|
||||
x_len = x_lens.max()
|
||||
x_attn_mask = torch.zeros((x_len, x_len), dtype=torch.bool)
|
||||
|
||||
kv_cache = None
|
||||
use_kv_caching = True
|
||||
|
||||
sum_logprobs = torch.zeros(best_of, device=y.device) # implement batch decoding here
|
||||
x = x.repeat(best_of, 1, 1)
|
||||
y = y.repeat(best_of, 1)
|
||||
while True:
|
||||
y_emb = self.ar_audio_embedding(y)
|
||||
y_emb = self.ar_audio_prenet(y_emb)
|
||||
y_pos = self.ar_audio_position(y_emb)
|
||||
xy_pos = torch.concat([x, y_pos], dim=1)
|
||||
|
||||
y_len = y.shape[1]
|
||||
x_attn_mask_pad = F.pad(
|
||||
x_attn_mask,
|
||||
(0, y_len),
|
||||
value=True,
|
||||
)
|
||||
y_attn_mask = F.pad(
|
||||
torch.triu(
|
||||
torch.ones(y_len, y_len, dtype=torch.bool), diagonal=1
|
||||
),
|
||||
(x_len, 0),
|
||||
value=False,
|
||||
)
|
||||
xy_attn_mask = torch.concat(
|
||||
[x_attn_mask_pad, y_attn_mask], dim=0
|
||||
).to(y.device)
|
||||
|
||||
|
||||
if use_kv_caching and kv_cache is not None:
|
||||
xy_pos = xy_pos[:, [-1]]
|
||||
else:
|
||||
pass
|
||||
|
||||
xy_dec, kv_cache = self.ar_decoder.infer(
|
||||
xy_pos,
|
||||
mask=xy_attn_mask,
|
||||
past_kv=kv_cache,
|
||||
use_cache=use_kv_caching,
|
||||
)
|
||||
# xy_dec, _ = self.ar_decoder(
|
||||
# (xy_pos, None),
|
||||
# mask=xy_attn_mask,
|
||||
# )
|
||||
|
||||
logits = self.ar_predict_layer(xy_dec[:, -1])
|
||||
samples, current_logprobs = topk_sampling(
|
||||
logits, top_k=top_k, top_p=1, temperature=temperature
|
||||
)
|
||||
sum_logprobs += current_logprobs * (y[:, -1] != NUM_AUDIO_TOKENS)
|
||||
samples[y[:, -1] == NUM_AUDIO_TOKENS] = NUM_AUDIO_TOKENS
|
||||
completed = (samples[:, -1] == NUM_AUDIO_TOKENS).all()
|
||||
if (
|
||||
completed
|
||||
or (y.shape[1] - prompts.shape[1]) > x_lens.max() * 16
|
||||
):
|
||||
if prompts.shape[1] == y.shape[1]:
|
||||
raise SyntaxError(
|
||||
"well trained model shouldn't reach here."
|
||||
)
|
||||
lengths = torch.sum(y != NUM_AUDIO_TOKENS, dim=1)
|
||||
avg_logprobs = sum_logprobs / lengths ** length_penalty
|
||||
# choose the best beam according to sum_logprobs
|
||||
best_beam = y[torch.argmax(avg_logprobs), :]
|
||||
worst_beam = y[torch.argmin(avg_logprobs), :]
|
||||
# strip all eos tokens
|
||||
best_beam = best_beam[best_beam != NUM_AUDIO_TOKENS]
|
||||
worst_beam = worst_beam[worst_beam != NUM_AUDIO_TOKENS]
|
||||
if return_worst:
|
||||
y = worst_beam.unsqueeze(0)
|
||||
else:
|
||||
y = best_beam.unsqueeze(0)
|
||||
print(f"VALL-E EOS [{prompts.shape[1]} -> {y.shape[1]}]")
|
||||
break
|
||||
|
||||
y = torch.concat([y, samples], dim=1)
|
||||
|
||||
codes = [y[:, prefix_len + int(self.ar_audio_prepend_bos) :]]
|
||||
if self.num_quantizers == 1:
|
||||
return torch.stack(codes, dim=-1)
|
||||
|
||||
# Non-AR Decoders
|
||||
y_emb = self.nar_audio_embeddings[0](
|
||||
y[:, int(self.ar_audio_prepend_bos) :]
|
||||
)
|
||||
|
||||
if self.prefix_mode in [2, 4]: # Exclude enrolled_phonemes
|
||||
enrolled_len = enroll_x_lens.max().item()
|
||||
# SOS + Synthesis Text + EOS
|
||||
text = torch.concat(
|
||||
[
|
||||
text[:, :1],
|
||||
text[:, enrolled_len - 1 :],
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
text_len = text_len - (enrolled_len - 2)
|
||||
assert text.shape[0] == 1
|
||||
|
||||
x = self.nar_text_embedding(text)
|
||||
# Add language embedding
|
||||
prompt_language_id = torch.LongTensor(np.array([self.language_ID[prompt_language]])).to(x.device)
|
||||
if isinstance(text_language, str):
|
||||
text_language_id = torch.LongTensor(np.array([self.language_ID[text_language]])).to(x.device)
|
||||
elif isinstance(text_language, List):
|
||||
text_language_id = torch.LongTensor(np.array([self.language_ID[tl] for tl in text_language])).to(x.device)
|
||||
x[:, :enroll_x_lens, :] += self.nar_language_embedding(prompt_language_id)
|
||||
x[:, enroll_x_lens:, :] += self.nar_language_embedding(text_language_id)
|
||||
x = self.nar_text_prenet(x)
|
||||
x = self.nar_text_position(x)
|
||||
|
||||
if self.prefix_mode == 0:
|
||||
for i, (predict_layer, embedding_layer) in enumerate(
|
||||
zip(
|
||||
self.nar_predict_layers,
|
||||
self.nar_audio_embeddings[1:],
|
||||
)
|
||||
):
|
||||
y_pos = self.nar_audio_prenet(y_emb)
|
||||
y_pos = self.nar_audio_position(y_pos)
|
||||
xy_pos = torch.concat([x, y_pos], dim=1)
|
||||
|
||||
xy_dec, _ = self.nar_decoder(
|
||||
(xy_pos, self.nar_stage_embeddings[i].weight)
|
||||
)
|
||||
logits = predict_layer(xy_dec[:, text_len + prefix_len :])
|
||||
|
||||
samples = torch.argmax(logits, dim=-1)
|
||||
codes.append(samples)
|
||||
|
||||
if i < self.num_quantizers - 2:
|
||||
y_emb[:, :prefix_len] += embedding_layer(
|
||||
prompts[..., i + 1]
|
||||
)
|
||||
y_emb[:, prefix_len:] += embedding_layer(samples)
|
||||
else:
|
||||
for j in range(1, self.num_quantizers):
|
||||
y_emb[:, :prefix_len] += self.nar_audio_embeddings[j](
|
||||
prompts[..., j]
|
||||
)
|
||||
|
||||
for i, (predict_layer, embedding_layer) in enumerate(
|
||||
zip(
|
||||
self.nar_predict_layers,
|
||||
self.nar_audio_embeddings[1:],
|
||||
)
|
||||
):
|
||||
y_pos = self.nar_audio_prenet(y_emb)
|
||||
y_pos = self.nar_audio_position(y_pos)
|
||||
xy_pos = torch.concat([x, y_pos], dim=1)
|
||||
|
||||
xy_dec, _ = self.nar_decoder(
|
||||
(xy_pos, self.nar_stage_embeddings[i].weight)
|
||||
)
|
||||
logits = predict_layer(xy_dec[:, text_len + prefix_len :])
|
||||
|
||||
samples = torch.argmax(logits, dim=-1)
|
||||
codes.append(samples)
|
||||
|
||||
if i < self.num_quantizers - 2:
|
||||
y_emb[:, prefix_len:] += embedding_layer(samples)
|
||||
|
||||
assert len(codes) == self.num_quantizers
|
||||
return torch.stack(codes, dim=-1)
|
||||
|
||||
def continual(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_lens: torch.Tensor,
|
||||
y: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Args:
|
||||
x:
|
||||
A 2-D tensor of shape (1, S).
|
||||
x_lens:
|
||||
A 1-D tensor of shape (1,). It contains the number of tokens in `x`
|
||||
before padding.
|
||||
y:
|
||||
A 3-D tensor of shape (1, T, 8).
|
||||
Returns:
|
||||
Return the predicted audio code matrix.
|
||||
"""
|
||||
assert x.ndim == 2, x.shape
|
||||
assert x_lens.ndim == 1, x_lens.shape
|
||||
assert y.ndim == 3, y.shape
|
||||
assert y.shape[0] == 1, y.shape
|
||||
|
||||
assert torch.all(x_lens > 0)
|
||||
assert self.num_quantizers == 8
|
||||
|
||||
# NOTE: x has been padded in TextTokenCollater
|
||||
text = x
|
||||
x = self.ar_text_embedding(text)
|
||||
x = self.ar_text_prenet(x)
|
||||
x = self.ar_text_position(x)
|
||||
|
||||
text_len = x_lens.max()
|
||||
|
||||
prefix_len = min(int(y.shape[1] * 0.5), 3 * 75)
|
||||
|
||||
# AR Decoder
|
||||
prompts = y[:, :prefix_len]
|
||||
|
||||
codes = [y[:, prefix_len:, 0]]
|
||||
# Non-AR Decoders
|
||||
x = self.nar_text_embedding(text)
|
||||
x = self.nar_text_prenet(x)
|
||||
x = self.nar_text_position(x)
|
||||
|
||||
y_emb = self.nar_audio_embeddings[0](y[..., 0])
|
||||
|
||||
if self.prefix_mode == 0:
|
||||
for i, (predict_layer, embedding_layer) in enumerate(
|
||||
zip(
|
||||
self.nar_predict_layers,
|
||||
self.nar_audio_embeddings[1:],
|
||||
)
|
||||
):
|
||||
y_pos = self.nar_audio_position(y_emb)
|
||||
y_pos = self.nar_audio_prenet(y_pos)
|
||||
xy_pos = torch.concat([x, y_pos], dim=1)
|
||||
|
||||
xy_dec, _ = self.nar_decoder(
|
||||
(xy_pos, self.nar_stage_embeddings[i].weight)
|
||||
)
|
||||
logits = predict_layer(xy_dec[:, text_len + prefix_len :])
|
||||
|
||||
samples = torch.argmax(logits, dim=-1)
|
||||
codes.append(samples)
|
||||
|
||||
if i < 6:
|
||||
y_emb[:, :prefix_len] += embedding_layer(
|
||||
prompts[..., i + 1]
|
||||
)
|
||||
y_emb[:, prefix_len:] += embedding_layer(samples)
|
||||
else:
|
||||
for j in range(1, 8):
|
||||
y_emb[:, :prefix_len] += self.nar_audio_embeddings[j](
|
||||
prompts[..., j]
|
||||
)
|
||||
|
||||
for i, (predict_layer, embedding_layer) in enumerate(
|
||||
zip(
|
||||
self.nar_predict_layers,
|
||||
self.nar_audio_embeddings[1:],
|
||||
)
|
||||
):
|
||||
y_pos = self.nar_audio_prenet(y_emb)
|
||||
y_pos = self.nar_audio_position(y_pos)
|
||||
xy_pos = torch.concat([x, y_pos], dim=1)
|
||||
|
||||
xy_dec, _ = self.nar_decoder(
|
||||
(xy_pos, self.nar_stage_embeddings[i].weight)
|
||||
)
|
||||
logits = predict_layer(xy_dec[:, text_len + prefix_len :])
|
||||
|
||||
samples = torch.argmax(logits, dim=-1)
|
||||
codes.append(samples)
|
||||
|
||||
if i < 6:
|
||||
y_emb[:, prefix_len:] += embedding_layer(samples)
|
||||
|
||||
assert len(codes) == 8
|
||||
return torch.stack(codes, dim=-1)
|
||||
|
||||
|
||||
# https://github.com/microsoft/unilm/blob/master/xtune/src/transformers/modeling_utils.py
|
||||
def top_k_top_p_filtering(
|
||||
logits, top_k=0, top_p=1.0, filter_value=-float("Inf"), min_tokens_to_keep=1
|
||||
):
|
||||
"""Filter a distribution of logits using top-k and/or nucleus (top-p) filtering
|
||||
Args:
|
||||
logits: logits distribution shape (batch size, vocabulary size)
|
||||
if top_k > 0: keep only top k tokens with highest probability (top-k filtering).
|
||||
if top_p < 1.0: keep the top tokens with cumulative probability >= top_p (nucleus filtering).
|
||||
Nucleus filtering is described in Holtzman et al. (http://arxiv.org/abs/1904.09751)
|
||||
Make sure we keep at least min_tokens_to_keep per batch example in the output
|
||||
From: https://gist.github.com/thomwolf/1a5a29f6962089e871b94cbd09daf317
|
||||
"""
|
||||
if top_k > 0:
|
||||
top_k = min(
|
||||
max(top_k, min_tokens_to_keep), logits.size(-1)
|
||||
) # Safety check
|
||||
# Remove all tokens with a probability less than the last token of the top-k
|
||||
indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
|
||||
logits[indices_to_remove] = filter_value
|
||||
|
||||
if top_p < 1.0:
|
||||
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
||||
cumulative_probs = torch.cumsum(
|
||||
F.softmax(sorted_logits, dim=-1), dim=-1
|
||||
)
|
||||
|
||||
# Remove tokens with cumulative probability above the threshold (token with 0 are kept)
|
||||
sorted_indices_to_remove = cumulative_probs > top_p
|
||||
if min_tokens_to_keep > 1:
|
||||
# Keep at least min_tokens_to_keep (set to min_tokens_to_keep-1 because we add the first one below)
|
||||
sorted_indices_to_remove[..., :min_tokens_to_keep] = 0
|
||||
# Shift the indices to the right to keep also the first token above the threshold
|
||||
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[
|
||||
..., :-1
|
||||
].clone()
|
||||
sorted_indices_to_remove[..., 0] = 0
|
||||
|
||||
# scatter sorted tensors to original indexing
|
||||
indices_to_remove = sorted_indices_to_remove.scatter(
|
||||
1, sorted_indices, sorted_indices_to_remove
|
||||
)
|
||||
logits[indices_to_remove] = filter_value
|
||||
return logits
|
||||
|
||||
|
||||
def topk_sampling(logits, top_k=10, top_p=1.0, temperature=1.0):
|
||||
# temperature: (`optional`) float
|
||||
# The value used to module the next token probabilities. Must be strictly positive. Default to 1.0.
|
||||
# top_k: (`optional`) int
|
||||
# The number of highest probability vocabulary tokens to keep for top-k-filtering. Between 1 and infinity. Default to 50.
|
||||
# top_p: (`optional`) float
|
||||
# The cumulative probability of parameter highest probability vocabulary tokens to keep for nucleus sampling. Must be between 0 and 1. Default to 1.
|
||||
|
||||
# Temperature (higher temperature => more likely to sample low probability tokens)
|
||||
if temperature != 1.0:
|
||||
logits = logits / temperature
|
||||
# Top-p/top-k filtering
|
||||
logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p)
|
||||
# Sample
|
||||
token = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1)
|
||||
logprobs = F.log_softmax(logits.float(), dim=-1)
|
||||
current_logprobs = logprobs[torch.arange(logprobs.shape[0]), token.squeeze(1)]
|
||||
return token, current_logprobs
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
def visualize(
|
||||
predicts: Tuple[torch.Tensor],
|
||||
batch: Dict[str, Union[List, torch.Tensor]],
|
||||
output_dir: str,
|
||||
limit: int = 4,
|
||||
) -> None:
|
||||
text_tokens = batch["text_tokens"].to("cpu").detach().numpy()
|
||||
text_tokens_lens = batch["text_tokens_lens"].to("cpu").detach().numpy()
|
||||
audio_features = batch["audio_features"].to("cpu").detach().numpy()
|
||||
audio_features_lens = (
|
||||
batch["audio_features_lens"].to("cpu").detach().numpy()
|
||||
)
|
||||
assert text_tokens.ndim == 2
|
||||
|
||||
utt_ids, texts = batch["utt_id"], batch["text"]
|
||||
|
||||
encoder_outputs = predicts[0].to("cpu").type(torch.float32).detach().numpy()
|
||||
decoder_outputs = predicts[1]
|
||||
if isinstance(decoder_outputs, list):
|
||||
decoder_outputs = decoder_outputs[-1]
|
||||
decoder_outputs = (
|
||||
decoder_outputs.to("cpu").type(torch.float32).detach().numpy()
|
||||
)
|
||||
|
||||
vmin, vmax = 0, 1024 # Encodec
|
||||
if decoder_outputs.dtype == np.float32:
|
||||
vmin, vmax = -6, 0 # Fbank
|
||||
|
||||
num_figures = 3
|
||||
for b, (utt_id, text) in enumerate(zip(utt_ids[:limit], texts[:limit])):
|
||||
_ = plt.figure(figsize=(14, 8 * num_figures))
|
||||
|
||||
S = text_tokens_lens[b]
|
||||
T = audio_features_lens[b]
|
||||
|
||||
# encoder
|
||||
plt.subplot(num_figures, 1, 1)
|
||||
plt.title(f"Text: {text}")
|
||||
plt.imshow(
|
||||
X=np.transpose(encoder_outputs[b]),
|
||||
cmap=plt.get_cmap("jet"),
|
||||
aspect="auto",
|
||||
interpolation="nearest",
|
||||
)
|
||||
plt.gca().invert_yaxis()
|
||||
plt.axvline(x=S - 0.4, linewidth=2, color="r")
|
||||
plt.xlabel("Encoder Output")
|
||||
plt.colorbar()
|
||||
|
||||
# decoder
|
||||
plt.subplot(num_figures, 1, 2)
|
||||
plt.imshow(
|
||||
X=np.transpose(decoder_outputs[b]),
|
||||
cmap=plt.get_cmap("jet"),
|
||||
aspect="auto",
|
||||
interpolation="nearest",
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
)
|
||||
plt.gca().invert_yaxis()
|
||||
plt.axvline(x=T - 0.4, linewidth=2, color="r")
|
||||
plt.xlabel("Decoder Output")
|
||||
plt.colorbar()
|
||||
|
||||
# target
|
||||
plt.subplot(num_figures, 1, 3)
|
||||
plt.imshow(
|
||||
X=np.transpose(audio_features[b]),
|
||||
cmap=plt.get_cmap("jet"),
|
||||
aspect="auto",
|
||||
interpolation="nearest",
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
)
|
||||
plt.gca().invert_yaxis()
|
||||
plt.axvline(x=T - 0.4, linewidth=2, color="r")
|
||||
plt.xlabel("Decoder Target")
|
||||
plt.colorbar()
|
||||
|
||||
plt.savefig(f"{output_dir}/{utt_id}.png")
|
||||
plt.close()
|
||||
@@ -0,0 +1,612 @@
|
||||
from typing import Optional, Tuple, List
|
||||
import math
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torch.nn import Linear, Module
|
||||
from torch.nn import functional as F
|
||||
from torch.nn.init import constant_, xavier_normal_, xavier_uniform_
|
||||
from torch.nn.modules.linear import NonDynamicallyQuantizableLinear
|
||||
from torch.nn.parameter import Parameter
|
||||
|
||||
def _in_projection_packed(
|
||||
q: Tensor,
|
||||
k: Tensor,
|
||||
v: Tensor,
|
||||
w: Tensor,
|
||||
b: Optional[Tensor] = None,
|
||||
) -> List[Tensor]:
|
||||
r"""
|
||||
Performs the in-projection step of the attention operation, using packed weights.
|
||||
Output is a triple containing projection tensors for query, key and value.
|
||||
|
||||
Args:
|
||||
q, k, v: query, key and value tensors to be projected. For self-attention,
|
||||
these are typically the same tensor; for encoder-decoder attention,
|
||||
k and v are typically the same tensor. (We take advantage of these
|
||||
identities for performance if they are present.) Regardless, q, k and v
|
||||
must share a common embedding dimension; otherwise their shapes may vary.
|
||||
w: projection weights for q, k and v, packed into a single tensor. Weights
|
||||
are packed along dimension 0, in q, k, v order.
|
||||
b: optional projection biases for q, k and v, packed into a single tensor
|
||||
in q, k, v order.
|
||||
|
||||
Shape:
|
||||
Inputs:
|
||||
- q: :math:`(..., E)` where E is the embedding dimension
|
||||
- k: :math:`(..., E)` where E is the embedding dimension
|
||||
- v: :math:`(..., E)` where E is the embedding dimension
|
||||
- w: :math:`(E * 3, E)` where E is the embedding dimension
|
||||
- b: :math:`E * 3` where E is the embedding dimension
|
||||
|
||||
Output:
|
||||
- in output list :math:`[q', k', v']`, each output tensor will have the
|
||||
same shape as the corresponding input tensor.
|
||||
"""
|
||||
E = q.size(-1)
|
||||
if k is v:
|
||||
if q is k:
|
||||
# self-attention
|
||||
return F.linear(q, w, b).chunk(3, dim=-1)
|
||||
else:
|
||||
# encoder-decoder attention
|
||||
w_q, w_kv = w.split([E, E * 2])
|
||||
if b is None:
|
||||
b_q = b_kv = None
|
||||
else:
|
||||
b_q, b_kv = b.split([E, E * 2])
|
||||
return (F.linear(q, w_q, b_q),) + F.linear(k, w_kv, b_kv).chunk(2, dim=-1)
|
||||
else:
|
||||
w_q, w_k, w_v = w.chunk(3)
|
||||
if b is None:
|
||||
b_q = b_k = b_v = None
|
||||
else:
|
||||
b_q, b_k, b_v = b.chunk(3)
|
||||
return F.linear(q, w_q, b_q), F.linear(k, w_k, b_k), F.linear(v, w_v, b_v)
|
||||
|
||||
def _scaled_dot_product_attention(
|
||||
q: Tensor,
|
||||
k: Tensor,
|
||||
v: Tensor,
|
||||
attn_mask: Optional[Tensor] = None,
|
||||
dropout_p: float = 0.0,
|
||||
) -> Tuple[Tensor, Tensor]:
|
||||
r"""
|
||||
Computes scaled dot product attention on query, key and value tensors, using
|
||||
an optional attention mask if passed, and applying dropout if a probability
|
||||
greater than 0.0 is specified.
|
||||
Returns a tensor pair containing attended values and attention weights.
|
||||
|
||||
Args:
|
||||
q, k, v: query, key and value tensors. See Shape section for shape details.
|
||||
attn_mask: optional tensor containing mask values to be added to calculated
|
||||
attention. May be 2D or 3D; see Shape section for details.
|
||||
dropout_p: dropout probability. If greater than 0.0, dropout is applied.
|
||||
|
||||
Shape:
|
||||
- q: :math:`(B, Nt, E)` where B is batch size, Nt is the target sequence length,
|
||||
and E is embedding dimension.
|
||||
- key: :math:`(B, Ns, E)` where B is batch size, Ns is the source sequence length,
|
||||
and E is embedding dimension.
|
||||
- value: :math:`(B, Ns, E)` where B is batch size, Ns is the source sequence length,
|
||||
and E is embedding dimension.
|
||||
- attn_mask: either a 3D tensor of shape :math:`(B, Nt, Ns)` or a 2D tensor of
|
||||
shape :math:`(Nt, Ns)`.
|
||||
|
||||
- Output: attention values have shape :math:`(B, Nt, E)`; attention weights
|
||||
have shape :math:`(B, Nt, Ns)`
|
||||
"""
|
||||
B, Nt, E = q.shape
|
||||
q = q / math.sqrt(E)
|
||||
# (B, Nt, E) x (B, E, Ns) -> (B, Nt, Ns)
|
||||
if attn_mask is not None:
|
||||
attn = torch.baddbmm(attn_mask, q, k.transpose(-2, -1))
|
||||
else:
|
||||
attn = torch.bmm(q, k.transpose(-2, -1))
|
||||
|
||||
attn = F.softmax(attn, dim=-1)
|
||||
if dropout_p > 0.0:
|
||||
attn = F.dropout(attn, p=dropout_p)
|
||||
# (B, Nt, Ns) x (B, Ns, E) -> (B, Nt, E)
|
||||
output = torch.bmm(attn, v)
|
||||
return output, attn
|
||||
|
||||
def multi_head_attention_forward(
|
||||
x,
|
||||
ipw,
|
||||
ipb,
|
||||
opw,
|
||||
opb,
|
||||
n_head,
|
||||
attn_mask,
|
||||
past_kv=None,
|
||||
use_cache=False,
|
||||
):
|
||||
# x = x.transpose(1, 0)
|
||||
# tgt_len, bsz, embed_dim = x.shape
|
||||
# head_dim = embed_dim // n_head
|
||||
# q, k, v = _in_projection_packed(x, x, x, ipw, ipb)
|
||||
# q = q.contiguous().view(tgt_len, bsz * n_head, head_dim).transpose(0, 1)
|
||||
# k = k.contiguous().view(k.shape[0], bsz * n_head, head_dim).transpose(0, 1)
|
||||
# v = v.contiguous().view(v.shape[0], bsz * n_head, head_dim).transpose(0, 1)
|
||||
|
||||
# new_attn_mask = torch.zeros_like(attn_mask, dtype=q.dtype)
|
||||
# new_attn_mask.masked_fill_(attn_mask, float("-inf"))
|
||||
# attn_mask = new_attn_mask
|
||||
#
|
||||
# attn_output, attn_output_weights = _scaled_dot_product_attention(q, k, v, attn_mask, 0.0)
|
||||
# attn_output = attn_output.transpose(0, 1).contiguous().view(tgt_len * bsz, embed_dim)
|
||||
# attn_output = torch._C._nn.linear(attn_output, opw, opb)
|
||||
# attn_output = attn_output.view(tgt_len, bsz, attn_output.size(1))
|
||||
|
||||
B, T, C = x.size()
|
||||
|
||||
q, k, v = torch._C._nn.linear(x, ipw, ipb).chunk(3, dim=-1)
|
||||
k = k.view(B, T, n_head, C // n_head).transpose(1, 2) # (B, nh, T, hs)
|
||||
q = q.view(B, T, n_head, C // n_head).transpose(1, 2) # (B, nh, T, hs)
|
||||
v = v.view(B, T, n_head, C // n_head).transpose(1, 2) # (B, nh, T, hs)
|
||||
if past_kv is not None:
|
||||
past_key = past_kv[0]
|
||||
past_value = past_kv[1]
|
||||
k = torch.cat((past_key, k), dim=-2)
|
||||
v = torch.cat((past_value, v), dim=-2)
|
||||
|
||||
FULL_T = k.shape[-2]
|
||||
|
||||
if use_cache is True:
|
||||
present = (k, v)
|
||||
else:
|
||||
present = None
|
||||
|
||||
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
|
||||
att = att.masked_fill(attn_mask[FULL_T - T:FULL_T, :FULL_T], float('-inf'))
|
||||
att = F.softmax(att, dim=-1)
|
||||
y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
|
||||
y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side
|
||||
y = torch._C._nn.linear(y, opw, opb)
|
||||
return (y, present)
|
||||
|
||||
|
||||
class MultiheadAttention(Module):
|
||||
r"""Allows the model to jointly attend to information
|
||||
from different representation subspaces as described in the paper:
|
||||
`Attention Is All You Need <https://arxiv.org/abs/1706.03762>`_.
|
||||
|
||||
Multi-Head Attention is defined as:
|
||||
|
||||
.. math::
|
||||
\text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O
|
||||
|
||||
where :math:`head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)`.
|
||||
|
||||
``forward()`` will use a special optimized implementation if all of the following
|
||||
conditions are met:
|
||||
|
||||
- self attention is being computed (i.e., ``query``, ``key``, and ``value`` are the same tensor. This
|
||||
restriction will be loosened in the future.)
|
||||
- Either autograd is disabled (using ``torch.inference_mode`` or ``torch.no_grad``) or no tensor argument ``requires_grad``
|
||||
- training is disabled (using ``.eval()``)
|
||||
- dropout is 0
|
||||
- ``add_bias_kv`` is ``False``
|
||||
- ``add_zero_attn`` is ``False``
|
||||
- ``batch_first`` is ``True`` and the input is batched
|
||||
- ``kdim`` and ``vdim`` are equal to ``embed_dim``
|
||||
- at most one of ``key_padding_mask`` or ``attn_mask`` is passed
|
||||
- if a `NestedTensor <https://pytorch.org/docs/stable/nested.html>`_ is passed, neither ``key_padding_mask``
|
||||
nor ``attn_mask`` is passed
|
||||
|
||||
If the optimized implementation is in use, a
|
||||
`NestedTensor <https://pytorch.org/docs/stable/nested.html>`_ can be passed for
|
||||
``query``/``key``/``value`` to represent padding more efficiently than using a
|
||||
padding mask. In this case, a `NestedTensor <https://pytorch.org/docs/stable/nested.html>`_
|
||||
will be returned, and an additional speedup proportional to the fraction of the input
|
||||
that is padding can be expected.
|
||||
|
||||
Args:
|
||||
embed_dim: Total dimension of the model.
|
||||
num_heads: Number of parallel attention heads. Note that ``embed_dim`` will be split
|
||||
across ``num_heads`` (i.e. each head will have dimension ``embed_dim // num_heads``).
|
||||
dropout: Dropout probability on ``attn_output_weights``. Default: ``0.0`` (no dropout).
|
||||
bias: If specified, adds bias to input / output projection layers. Default: ``True``.
|
||||
add_bias_kv: If specified, adds bias to the key and value sequences at dim=0. Default: ``False``.
|
||||
add_zero_attn: If specified, adds a new batch of zeros to the key and value sequences at dim=1.
|
||||
Default: ``False``.
|
||||
kdim: Total number of features for keys. Default: ``None`` (uses ``kdim=embed_dim``).
|
||||
vdim: Total number of features for values. Default: ``None`` (uses ``vdim=embed_dim``).
|
||||
batch_first: If ``True``, then the input and output tensors are provided
|
||||
as (batch, seq, feature). Default: ``False`` (seq, batch, feature).
|
||||
|
||||
Examples::
|
||||
|
||||
>>> # xdoctest: +SKIP
|
||||
>>> multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
|
||||
>>> attn_output, attn_output_weights = multihead_attn(query, key, value)
|
||||
|
||||
"""
|
||||
__constants__ = ["batch_first"]
|
||||
bias_k: Optional[torch.Tensor]
|
||||
bias_v: Optional[torch.Tensor]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim,
|
||||
num_heads,
|
||||
dropout=0.0,
|
||||
bias=True,
|
||||
add_bias_kv=False,
|
||||
add_zero_attn=False,
|
||||
kdim=None,
|
||||
vdim=None,
|
||||
batch_first=False,
|
||||
linear1_cls=Linear,
|
||||
linear2_cls=Linear,
|
||||
device=None,
|
||||
dtype=None,
|
||||
) -> None:
|
||||
factory_kwargs = {"device": device, "dtype": dtype}
|
||||
super(MultiheadAttention, self).__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.kdim = kdim if kdim is not None else embed_dim
|
||||
self.vdim = vdim if vdim is not None else embed_dim
|
||||
self._qkv_same_embed_dim = (
|
||||
self.kdim == embed_dim and self.vdim == embed_dim
|
||||
)
|
||||
|
||||
self.num_heads = num_heads
|
||||
self.dropout = dropout
|
||||
self.batch_first = batch_first
|
||||
self.head_dim = embed_dim // num_heads
|
||||
assert (
|
||||
self.head_dim * num_heads == self.embed_dim
|
||||
), "embed_dim must be divisible by num_heads"
|
||||
|
||||
if add_bias_kv:
|
||||
self.bias_k = Parameter(
|
||||
torch.empty((1, 1, embed_dim), **factory_kwargs)
|
||||
)
|
||||
self.bias_v = Parameter(
|
||||
torch.empty((1, 1, embed_dim), **factory_kwargs)
|
||||
)
|
||||
else:
|
||||
self.bias_k = self.bias_v = None
|
||||
|
||||
if linear1_cls == Linear:
|
||||
if not self._qkv_same_embed_dim:
|
||||
self.q_proj_weight = Parameter(
|
||||
torch.empty((embed_dim, embed_dim), **factory_kwargs)
|
||||
)
|
||||
self.k_proj_weight = Parameter(
|
||||
torch.empty((embed_dim, self.kdim), **factory_kwargs)
|
||||
)
|
||||
self.v_proj_weight = Parameter(
|
||||
torch.empty((embed_dim, self.vdim), **factory_kwargs)
|
||||
)
|
||||
self.register_parameter("in_proj_weight", None)
|
||||
else:
|
||||
self.in_proj_weight = Parameter(
|
||||
torch.empty((3 * embed_dim, embed_dim), **factory_kwargs)
|
||||
)
|
||||
self.register_parameter("q_proj_weight", None)
|
||||
self.register_parameter("k_proj_weight", None)
|
||||
self.register_parameter("v_proj_weight", None)
|
||||
|
||||
if bias:
|
||||
self.in_proj_bias = Parameter(
|
||||
torch.empty(3 * embed_dim, **factory_kwargs)
|
||||
)
|
||||
else:
|
||||
self.register_parameter("in_proj_bias", None)
|
||||
self.out_proj = NonDynamicallyQuantizableLinear(
|
||||
embed_dim, embed_dim, bias=bias, **factory_kwargs
|
||||
)
|
||||
|
||||
self._reset_parameters()
|
||||
else:
|
||||
if not self._qkv_same_embed_dim:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
self.in_proj_linear = linear1_cls(
|
||||
embed_dim, 3 * embed_dim, bias=bias, **factory_kwargs
|
||||
)
|
||||
self.in_proj_weight = self.in_proj_linear.weight
|
||||
|
||||
self.register_parameter("q_proj_weight", None)
|
||||
self.register_parameter("k_proj_weight", None)
|
||||
self.register_parameter("v_proj_weight", None)
|
||||
|
||||
if bias:
|
||||
self.in_proj_bias = self.in_proj_linear.bias
|
||||
else:
|
||||
self.register_parameter("in_proj_bias", None)
|
||||
|
||||
self.out_proj = linear2_cls(
|
||||
embed_dim, embed_dim, bias=bias, **factory_kwargs
|
||||
)
|
||||
|
||||
if self.bias_k is not None:
|
||||
xavier_normal_(self.bias_k)
|
||||
if self.bias_v is not None:
|
||||
xavier_normal_(self.bias_v)
|
||||
|
||||
self.add_zero_attn = add_zero_attn
|
||||
|
||||
def _reset_parameters(self):
|
||||
if self._qkv_same_embed_dim:
|
||||
xavier_uniform_(self.in_proj_weight)
|
||||
else:
|
||||
xavier_uniform_(self.q_proj_weight)
|
||||
xavier_uniform_(self.k_proj_weight)
|
||||
xavier_uniform_(self.v_proj_weight)
|
||||
|
||||
if self.in_proj_bias is not None:
|
||||
constant_(self.in_proj_bias, 0.0)
|
||||
constant_(self.out_proj.bias, 0.0)
|
||||
|
||||
if self.bias_k is not None:
|
||||
xavier_normal_(self.bias_k)
|
||||
if self.bias_v is not None:
|
||||
xavier_normal_(self.bias_v)
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Support loading old MultiheadAttention checkpoints generated by v1.1.0
|
||||
if "_qkv_same_embed_dim" not in state:
|
||||
state["_qkv_same_embed_dim"] = True
|
||||
|
||||
super(MultiheadAttention, self).__setstate__(state)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
query: Tensor,
|
||||
key: Tensor,
|
||||
value: Tensor,
|
||||
key_padding_mask: Optional[Tensor] = None,
|
||||
need_weights: bool = True,
|
||||
attn_mask: Optional[Tensor] = None,
|
||||
average_attn_weights: bool = True,
|
||||
) -> Tuple[Tensor, Optional[Tensor]]:
|
||||
r"""
|
||||
Args:
|
||||
query: Query embeddings of shape :math:`(L, E_q)` for unbatched input, :math:`(L, N, E_q)` when ``batch_first=False``
|
||||
or :math:`(N, L, E_q)` when ``batch_first=True``, where :math:`L` is the target sequence length,
|
||||
:math:`N` is the batch size, and :math:`E_q` is the query embedding dimension ``embed_dim``.
|
||||
Queries are compared against key-value pairs to produce the output.
|
||||
See "Attention Is All You Need" for more details.
|
||||
key: Key embeddings of shape :math:`(S, E_k)` for unbatched input, :math:`(S, N, E_k)` when ``batch_first=False``
|
||||
or :math:`(N, S, E_k)` when ``batch_first=True``, where :math:`S` is the source sequence length,
|
||||
:math:`N` is the batch size, and :math:`E_k` is the key embedding dimension ``kdim``.
|
||||
See "Attention Is All You Need" for more details.
|
||||
value: Value embeddings of shape :math:`(S, E_v)` for unbatched input, :math:`(S, N, E_v)` when
|
||||
``batch_first=False`` or :math:`(N, S, E_v)` when ``batch_first=True``, where :math:`S` is the source
|
||||
sequence length, :math:`N` is the batch size, and :math:`E_v` is the value embedding dimension ``vdim``.
|
||||
See "Attention Is All You Need" for more details.
|
||||
key_padding_mask: If specified, a mask of shape :math:`(N, S)` indicating which elements within ``key``
|
||||
to ignore for the purpose of attention (i.e. treat as "padding"). For unbatched `query`, shape should be :math:`(S)`.
|
||||
Binary and byte masks are supported.
|
||||
For a binary mask, a ``True`` value indicates that the corresponding ``key`` value will be ignored for
|
||||
the purpose of attention. For a float mask, it will be directly added to the corresponding ``key`` value.
|
||||
need_weights: If specified, returns ``attn_output_weights`` in addition to ``attn_outputs``.
|
||||
Default: ``True``.
|
||||
attn_mask: If specified, a 2D or 3D mask preventing attention to certain positions. Must be of shape
|
||||
:math:`(L, S)` or :math:`(N\cdot\text{num\_heads}, L, S)`, where :math:`N` is the batch size,
|
||||
:math:`L` is the target sequence length, and :math:`S` is the source sequence length. A 2D mask will be
|
||||
broadcasted across the batch while a 3D mask allows for a different mask for each entry in the batch.
|
||||
Binary, byte, and float masks are supported. For a binary mask, a ``True`` value indicates that the
|
||||
corresponding position is not allowed to attend. For a byte mask, a non-zero value indicates that the
|
||||
corresponding position is not allowed to attend. For a float mask, the mask values will be added to
|
||||
the attention weight.
|
||||
average_attn_weights: If true, indicates that the returned ``attn_weights`` should be averaged across
|
||||
heads. Otherwise, ``attn_weights`` are provided separately per head. Note that this flag only has an
|
||||
effect when ``need_weights=True``. Default: ``True`` (i.e. average weights across heads)
|
||||
|
||||
Outputs:
|
||||
- **attn_output** - Attention outputs of shape :math:`(L, E)` when input is unbatched,
|
||||
:math:`(L, N, E)` when ``batch_first=False`` or :math:`(N, L, E)` when ``batch_first=True``,
|
||||
where :math:`L` is the target sequence length, :math:`N` is the batch size, and :math:`E` is the
|
||||
embedding dimension ``embed_dim``.
|
||||
- **attn_output_weights** - Only returned when ``need_weights=True``. If ``average_attn_weights=True``,
|
||||
returns attention weights averaged across heads of shape :math:`(L, S)` when input is unbatched or
|
||||
:math:`(N, L, S)`, where :math:`N` is the batch size, :math:`L` is the target sequence length, and
|
||||
:math:`S` is the source sequence length. If ``average_attn_weights=False``, returns attention weights per
|
||||
head of shape :math:`(\text{num\_heads}, L, S)` when input is unbatched or :math:`(N, \text{num\_heads}, L, S)`.
|
||||
|
||||
.. note::
|
||||
`batch_first` argument is ignored for unbatched inputs.
|
||||
"""
|
||||
is_batched = query.dim() == 3
|
||||
if key_padding_mask is not None:
|
||||
_kpm_dtype = key_padding_mask.dtype
|
||||
if _kpm_dtype != torch.bool and not torch.is_floating_point(
|
||||
key_padding_mask
|
||||
):
|
||||
raise AssertionError(
|
||||
"only bool and floating types of key_padding_mask are supported"
|
||||
)
|
||||
why_not_fast_path = ""
|
||||
if not is_batched:
|
||||
why_not_fast_path = f"input not batched; expected query.dim() of 3 but got {query.dim()}"
|
||||
elif query is not key or key is not value:
|
||||
# When lifting this restriction, don't forget to either
|
||||
# enforce that the dtypes all match or test cases where
|
||||
# they don't!
|
||||
why_not_fast_path = "non-self attention was used (query, key, and value are not the same Tensor)"
|
||||
elif (
|
||||
self.in_proj_bias is not None
|
||||
and query.dtype != self.in_proj_bias.dtype
|
||||
):
|
||||
why_not_fast_path = f"dtypes of query ({query.dtype}) and self.in_proj_bias ({self.in_proj_bias.dtype}) don't match"
|
||||
elif (
|
||||
self.in_proj_weight is not None
|
||||
and query.dtype != self.in_proj_weight.dtype
|
||||
):
|
||||
# this case will fail anyway, but at least they'll get a useful error message.
|
||||
why_not_fast_path = f"dtypes of query ({query.dtype}) and self.in_proj_weight ({self.in_proj_weight.dtype}) don't match"
|
||||
elif self.training:
|
||||
why_not_fast_path = "training is enabled"
|
||||
elif not self.batch_first:
|
||||
why_not_fast_path = "batch_first was not True"
|
||||
elif self.bias_k is not None:
|
||||
why_not_fast_path = "self.bias_k was not None"
|
||||
elif self.bias_v is not None:
|
||||
why_not_fast_path = "self.bias_v was not None"
|
||||
elif self.dropout:
|
||||
why_not_fast_path = f"dropout was {self.dropout}, required zero"
|
||||
elif self.add_zero_attn:
|
||||
why_not_fast_path = "add_zero_attn was enabled"
|
||||
elif not self._qkv_same_embed_dim:
|
||||
why_not_fast_path = "_qkv_same_embed_dim was not True"
|
||||
elif attn_mask is not None:
|
||||
why_not_fast_path = "attn_mask was not None"
|
||||
elif query.is_nested and key_padding_mask is not None:
|
||||
why_not_fast_path = (
|
||||
"key_padding_mask is not supported with NestedTensor input"
|
||||
)
|
||||
elif self.num_heads % 2 == 1:
|
||||
why_not_fast_path = "num_heads is odd"
|
||||
elif torch.is_autocast_enabled():
|
||||
why_not_fast_path = "autocast is enabled"
|
||||
|
||||
if not why_not_fast_path:
|
||||
tensor_args = (
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
self.in_proj_weight,
|
||||
self.in_proj_bias,
|
||||
self.out_proj.weight,
|
||||
self.out_proj.bias,
|
||||
)
|
||||
# We have to use list comprehensions below because TorchScript does not support
|
||||
# generator expressions.
|
||||
if torch.overrides.has_torch_function(tensor_args):
|
||||
why_not_fast_path = "some Tensor argument has_torch_function"
|
||||
elif not all(
|
||||
[
|
||||
(x is None or x.is_cuda or "cpu" in str(x.device))
|
||||
for x in tensor_args
|
||||
]
|
||||
):
|
||||
why_not_fast_path = (
|
||||
"some Tensor argument is neither CUDA nor CPU"
|
||||
)
|
||||
elif torch.is_grad_enabled() and any(
|
||||
[x is not None and x.requires_grad for x in tensor_args]
|
||||
):
|
||||
why_not_fast_path = (
|
||||
"grad is enabled and at least one of query or the "
|
||||
"input/output projection weights or biases requires_grad"
|
||||
)
|
||||
if not why_not_fast_path:
|
||||
return torch._native_multi_head_attention(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
self.embed_dim,
|
||||
self.num_heads,
|
||||
self.in_proj_weight,
|
||||
self.in_proj_bias,
|
||||
self.out_proj.weight,
|
||||
self.out_proj.bias,
|
||||
key_padding_mask
|
||||
if key_padding_mask is not None
|
||||
else attn_mask,
|
||||
need_weights,
|
||||
average_attn_weights,
|
||||
1
|
||||
if key_padding_mask is not None
|
||||
else 0
|
||||
if attn_mask is not None
|
||||
else None,
|
||||
)
|
||||
|
||||
any_nested = query.is_nested or key.is_nested or value.is_nested
|
||||
assert not any_nested, (
|
||||
"MultiheadAttention does not support NestedTensor outside of its fast path. "
|
||||
+ f"The fast path was not hit because {why_not_fast_path}"
|
||||
)
|
||||
|
||||
if self.batch_first and is_batched:
|
||||
# make sure that the transpose op does not affect the "is" property
|
||||
if key is value:
|
||||
if query is key:
|
||||
query = key = value = query.transpose(1, 0)
|
||||
else:
|
||||
query, key = [x.transpose(1, 0) for x in (query, key)]
|
||||
value = key
|
||||
else:
|
||||
query, key, value = [
|
||||
x.transpose(1, 0) for x in (query, key, value)
|
||||
]
|
||||
|
||||
if not self._qkv_same_embed_dim:
|
||||
attn_output, attn_output_weights = F.multi_head_attention_forward(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
self.embed_dim,
|
||||
self.num_heads,
|
||||
self.in_proj_weight,
|
||||
self.in_proj_bias,
|
||||
self.bias_k,
|
||||
self.bias_v,
|
||||
self.add_zero_attn,
|
||||
self.dropout,
|
||||
self.out_proj.weight,
|
||||
self.out_proj.bias,
|
||||
training=self.training,
|
||||
key_padding_mask=key_padding_mask,
|
||||
need_weights=need_weights,
|
||||
attn_mask=attn_mask,
|
||||
use_separate_proj_weight=True,
|
||||
q_proj_weight=self.q_proj_weight,
|
||||
k_proj_weight=self.k_proj_weight,
|
||||
v_proj_weight=self.v_proj_weight,
|
||||
average_attn_weights=average_attn_weights,
|
||||
)
|
||||
else:
|
||||
attn_output, attn_output_weights = F.multi_head_attention_forward(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
self.embed_dim,
|
||||
self.num_heads,
|
||||
self.in_proj_weight,
|
||||
self.in_proj_bias,
|
||||
self.bias_k,
|
||||
self.bias_v,
|
||||
self.add_zero_attn,
|
||||
self.dropout,
|
||||
self.out_proj.weight,
|
||||
self.out_proj.bias,
|
||||
training=self.training,
|
||||
key_padding_mask=key_padding_mask,
|
||||
need_weights=need_weights,
|
||||
attn_mask=attn_mask,
|
||||
average_attn_weights=average_attn_weights,
|
||||
)
|
||||
if self.batch_first and is_batched:
|
||||
return attn_output.transpose(1, 0), attn_output_weights
|
||||
else:
|
||||
return attn_output, attn_output_weights
|
||||
|
||||
def infer(self,
|
||||
x: Tensor,
|
||||
key_padding_mask: Optional[Tensor] = None,
|
||||
need_weights: bool = True,
|
||||
attn_mask: Optional[Tensor] = None,
|
||||
average_attn_weights: bool = True,
|
||||
past_kv = None,
|
||||
use_cache = False
|
||||
):
|
||||
# x = x.transpose(1, 0)
|
||||
y, kv = multi_head_attention_forward(
|
||||
x=x,
|
||||
ipw=self.in_proj_weight,
|
||||
ipb=self.in_proj_bias,
|
||||
opw=self.out_proj.weight,
|
||||
opb=self.out_proj.bias,
|
||||
n_head=self.num_heads,
|
||||
attn_mask=attn_mask,
|
||||
past_kv=past_kv,
|
||||
use_cache=use_cache,
|
||||
)
|
||||
return (y, kv)
|
||||
@@ -0,0 +1,97 @@
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class TokenEmbedding(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim_model: int,
|
||||
vocab_size: int,
|
||||
dropout: float = 0.0,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.vocab_size = vocab_size
|
||||
self.dim_model = dim_model
|
||||
|
||||
self.dropout = torch.nn.Dropout(p=dropout)
|
||||
self.word_embeddings = nn.Embedding(self.vocab_size, self.dim_model)
|
||||
|
||||
@property
|
||||
def weight(self) -> torch.Tensor:
|
||||
return self.word_embeddings.weight
|
||||
|
||||
def embedding(self, index: int) -> torch.Tensor:
|
||||
return self.word_embeddings.weight[index : index + 1]
|
||||
|
||||
def forward(self, x: torch.Tensor):
|
||||
X = self.word_embeddings(x)
|
||||
X = self.dropout(X)
|
||||
|
||||
return X
|
||||
|
||||
|
||||
class SinePositionalEmbedding(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim_model: int,
|
||||
dropout: float = 0.0,
|
||||
scale: bool = False,
|
||||
alpha: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.dim_model = dim_model
|
||||
self.x_scale = math.sqrt(dim_model) if scale else 1.0
|
||||
self.alpha = nn.Parameter(torch.ones(1), requires_grad=alpha)
|
||||
self.dropout = torch.nn.Dropout(p=dropout)
|
||||
|
||||
self.reverse = False
|
||||
self.pe = None
|
||||
self.extend_pe(torch.tensor(0.0).expand(1, 4000))
|
||||
|
||||
def extend_pe(self, x):
|
||||
"""Reset the positional encodings."""
|
||||
if self.pe is not None:
|
||||
if self.pe.size(1) >= x.size(1):
|
||||
if self.pe.dtype != x.dtype or self.pe.device != x.device:
|
||||
self.pe = self.pe.to(dtype=x.dtype, device=x.device)
|
||||
return
|
||||
pe = torch.zeros(x.size(1), self.dim_model)
|
||||
if self.reverse:
|
||||
position = torch.arange(
|
||||
x.size(1) - 1, -1, -1.0, dtype=torch.float32
|
||||
).unsqueeze(1)
|
||||
else:
|
||||
position = torch.arange(
|
||||
0, x.size(1), dtype=torch.float32
|
||||
).unsqueeze(1)
|
||||
div_term = torch.exp(
|
||||
torch.arange(0, self.dim_model, 2, dtype=torch.float32)
|
||||
* -(math.log(10000.0) / self.dim_model)
|
||||
)
|
||||
pe[:, 0::2] = torch.sin(position * div_term)
|
||||
pe[:, 1::2] = torch.cos(position * div_term)
|
||||
pe = pe.unsqueeze(0)
|
||||
self.pe = pe.to(device=x.device, dtype=x.dtype).detach()
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
self.extend_pe(x)
|
||||
output = x.unsqueeze(-1) if x.ndim == 2 else x
|
||||
output = output * self.x_scale + self.alpha * self.pe[:, : x.size(1)]
|
||||
return self.dropout(output)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2023 (authors: Feiteng Li)
|
||||
#
|
||||
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import torch
|
||||
|
||||
from modules.optim import Eden
|
||||
|
||||
|
||||
def calc_lr(step, dim_embed, warmup_steps):
|
||||
return dim_embed ** (-0.5) * min(
|
||||
step ** (-0.5), step * warmup_steps ** (-1.5)
|
||||
)
|
||||
|
||||
|
||||
class NoamScheduler(torch.optim.lr_scheduler._LRScheduler):
|
||||
def __init__(
|
||||
self,
|
||||
base_lr: float,
|
||||
optimizer: torch.optim.Optimizer,
|
||||
dim_embed: int,
|
||||
warmup_steps: int,
|
||||
last_epoch: int = -1,
|
||||
verbose: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.dim_embed = dim_embed
|
||||
self.base_lr = base_lr
|
||||
self.warmup_steps = warmup_steps
|
||||
self.num_param_groups = len(optimizer.param_groups)
|
||||
|
||||
super().__init__(optimizer, last_epoch, verbose)
|
||||
|
||||
def get_lr(self) -> float:
|
||||
lr = self.base_lr * calc_lr(
|
||||
self._step_count, self.dim_embed, self.warmup_steps
|
||||
)
|
||||
return [lr] * self.num_param_groups
|
||||
|
||||
def set_step(self, step: int):
|
||||
self._step_count = step
|
||||
|
||||
|
||||
def get_scheduler(params, optimizer):
|
||||
if params.scheduler_name.lower() == "eden":
|
||||
scheduler = Eden(optimizer, 5000, 4, warmup_batches=params.warmup_steps)
|
||||
elif params.scheduler_name.lower() == "noam":
|
||||
scheduler = NoamScheduler(
|
||||
params.base_lr,
|
||||
optimizer,
|
||||
params.decoder_dim,
|
||||
warmup_steps=params.warmup_steps,
|
||||
)
|
||||
# scheduler.set_step(params.start_batch or params.batch_idx_train)
|
||||
elif params.scheduler_name.lower() == "cosine":
|
||||
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
|
||||
params.warmup_steps,
|
||||
optimizer,
|
||||
eta_min=params.base_lr,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"{params.scheduler_name}")
|
||||
|
||||
return scheduler
|
||||
@@ -0,0 +1,683 @@
|
||||
import copy
|
||||
import numbers
|
||||
from functools import partial
|
||||
from typing import Any, Callable, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
from torch import Tensor, nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
from .activation import MultiheadAttention
|
||||
from .scaling import ActivationBalancer, BalancedDoubleSwish
|
||||
from .scaling import BasicNorm as _BasicNorm
|
||||
|
||||
_shape_t = Union[int, List[int], torch.Size]
|
||||
|
||||
|
||||
class LayerNorm(nn.Module):
|
||||
__constants__ = ["normalized_shape", "eps", "elementwise_affine"]
|
||||
normalized_shape: Tuple[int, ...]
|
||||
eps: float
|
||||
elementwise_affine: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
normalized_shape: _shape_t,
|
||||
eps: float = 1e-5,
|
||||
elementwise_affine: bool = True,
|
||||
device=None,
|
||||
dtype=None,
|
||||
) -> None:
|
||||
factory_kwargs = {"device": device, "dtype": dtype}
|
||||
super(LayerNorm, self).__init__()
|
||||
if isinstance(normalized_shape, numbers.Integral):
|
||||
# mypy error: incompatible types in assignment
|
||||
normalized_shape = (normalized_shape,) # type: ignore[assignment]
|
||||
self.normalized_shape = tuple(normalized_shape) # type: ignore[arg-type]
|
||||
self.eps = eps
|
||||
self.elementwise_affine = elementwise_affine
|
||||
if self.elementwise_affine:
|
||||
self.weight = nn.Parameter(
|
||||
torch.empty(self.normalized_shape, **factory_kwargs)
|
||||
)
|
||||
self.bias = nn.Parameter(
|
||||
torch.empty(self.normalized_shape, **factory_kwargs)
|
||||
)
|
||||
else:
|
||||
self.register_parameter("weight", None)
|
||||
self.register_parameter("bias", None)
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self) -> None:
|
||||
if self.elementwise_affine:
|
||||
nn.init.ones_(self.weight)
|
||||
nn.init.zeros_(self.bias)
|
||||
|
||||
def forward(self, input: Tensor, embedding: Any = None) -> Tensor:
|
||||
if isinstance(input, tuple):
|
||||
input, embedding = input
|
||||
return (
|
||||
F.layer_norm(
|
||||
input,
|
||||
self.normalized_shape,
|
||||
self.weight,
|
||||
self.bias,
|
||||
self.eps,
|
||||
),
|
||||
embedding,
|
||||
)
|
||||
|
||||
assert embedding is None
|
||||
return F.layer_norm(
|
||||
input, self.normalized_shape, self.weight, self.bias, self.eps
|
||||
)
|
||||
|
||||
def extra_repr(self) -> str:
|
||||
return (
|
||||
"{normalized_shape}, eps={eps}, "
|
||||
"elementwise_affine={elementwise_affine}".format(**self.__dict__)
|
||||
)
|
||||
|
||||
|
||||
class AdaptiveLayerNorm(nn.Module):
|
||||
r"""Adaptive Layer Normalization"""
|
||||
|
||||
def __init__(self, d_model, norm) -> None:
|
||||
super(AdaptiveLayerNorm, self).__init__()
|
||||
self.project_layer = nn.Linear(d_model, 2 * d_model)
|
||||
self.norm = norm
|
||||
self.d_model = d_model
|
||||
self.eps = self.norm.eps
|
||||
|
||||
def forward(self, input: Tensor, embedding: Tensor = None) -> Tensor:
|
||||
if isinstance(input, tuple):
|
||||
input, embedding = input
|
||||
weight, bias = torch.split(
|
||||
self.project_layer(embedding),
|
||||
split_size_or_sections=self.d_model,
|
||||
dim=-1,
|
||||
)
|
||||
return (weight * self.norm(input) + bias, embedding)
|
||||
|
||||
weight, bias = torch.split(
|
||||
self.project_layer(embedding),
|
||||
split_size_or_sections=self.d_model,
|
||||
dim=-1,
|
||||
)
|
||||
return weight * self.norm(input) + bias
|
||||
|
||||
|
||||
class BasicNorm(_BasicNorm):
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
eps: float = 1e-5,
|
||||
device=None,
|
||||
dtype=None,
|
||||
):
|
||||
super(BasicNorm, self).__init__(d_model, eps=eps)
|
||||
|
||||
def forward(self, input: Tensor, embedding: Any = None) -> Tensor:
|
||||
if isinstance(input, tuple):
|
||||
input, embedding = input
|
||||
return (
|
||||
super(BasicNorm, self).forward(input),
|
||||
embedding,
|
||||
)
|
||||
|
||||
assert embedding is None
|
||||
return super(BasicNorm, self).forward(input)
|
||||
|
||||
|
||||
class BalancedBasicNorm(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
eps: float = 1e-5,
|
||||
device=None,
|
||||
dtype=None,
|
||||
):
|
||||
super(BalancedBasicNorm, self).__init__()
|
||||
self.balancer = ActivationBalancer(
|
||||
d_model,
|
||||
channel_dim=-1,
|
||||
min_positive=0.45,
|
||||
max_positive=0.55,
|
||||
max_abs=6.0,
|
||||
)
|
||||
self.norm = BasicNorm(d_model, eps, device=device, dtype=dtype)
|
||||
|
||||
def forward(self, input: Tensor, embedding: Any = None) -> Tensor:
|
||||
if isinstance(input, tuple):
|
||||
input, embedding = input
|
||||
return self.norm((self.balancer(input), embedding))
|
||||
|
||||
assert embedding is None
|
||||
return self.norm(self.balancer(input))
|
||||
|
||||
|
||||
class IdentityNorm(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
eps: float = 1e-5,
|
||||
device=None,
|
||||
dtype=None,
|
||||
) -> None:
|
||||
super(IdentityNorm, self).__init__()
|
||||
|
||||
def forward(self, input: Tensor, embedding: Any = None) -> Tensor:
|
||||
if isinstance(input, tuple):
|
||||
return input
|
||||
|
||||
assert embedding is None
|
||||
return input
|
||||
|
||||
|
||||
class TransformerEncoderLayer(nn.Module):
|
||||
__constants__ = ["batch_first", "norm_first"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
nhead: int,
|
||||
dim_feedforward: int = 2048,
|
||||
dropout: float = 0.1,
|
||||
activation: Union[str, Callable[[Tensor], Tensor]] = F.relu,
|
||||
batch_first: bool = False,
|
||||
norm_first: bool = False,
|
||||
device=None,
|
||||
dtype=None,
|
||||
linear1_self_attention_cls: nn.Module = nn.Linear,
|
||||
linear2_self_attention_cls: nn.Module = nn.Linear,
|
||||
linear1_feedforward_cls: nn.Module = nn.Linear,
|
||||
linear2_feedforward_cls: nn.Module = nn.Linear,
|
||||
layer_norm_cls: nn.Module = LayerNorm,
|
||||
layer_norm_eps: float = 1e-5,
|
||||
adaptive_layer_norm=False,
|
||||
) -> None:
|
||||
factory_kwargs = {"device": device, "dtype": dtype}
|
||||
super(TransformerEncoderLayer, self).__init__()
|
||||
self.self_attn = MultiheadAttention(
|
||||
d_model,
|
||||
nhead,
|
||||
dropout=dropout,
|
||||
batch_first=batch_first,
|
||||
linear1_cls=linear1_self_attention_cls,
|
||||
linear2_cls=linear2_self_attention_cls,
|
||||
**factory_kwargs,
|
||||
)
|
||||
|
||||
# Implementation of Feedforward model
|
||||
self.linear1 = linear1_feedforward_cls(
|
||||
d_model, dim_feedforward, **factory_kwargs
|
||||
)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.linear2 = linear2_feedforward_cls(
|
||||
dim_feedforward, d_model, **factory_kwargs
|
||||
)
|
||||
|
||||
self.norm_first = norm_first
|
||||
self.dropout1 = nn.Dropout(dropout)
|
||||
self.dropout2 = nn.Dropout(dropout)
|
||||
|
||||
# Legacy string support for activation function.
|
||||
if isinstance(activation, str):
|
||||
activation = _get_activation_fn(activation)
|
||||
elif isinstance(activation, partial):
|
||||
activation = activation(d_model)
|
||||
elif activation == BalancedDoubleSwish:
|
||||
activation = BalancedDoubleSwish(d_model)
|
||||
|
||||
# # We can't test self.activation in forward() in TorchScript,
|
||||
# # so stash some information about it instead.
|
||||
# if activation is F.relu or isinstance(activation, torch.nn.ReLU):
|
||||
# self.activation_relu_or_gelu = 1
|
||||
# elif activation is F.gelu or isinstance(activation, torch.nn.GELU):
|
||||
# self.activation_relu_or_gelu = 2
|
||||
# else:
|
||||
# self.activation_relu_or_gelu = 0
|
||||
self.activation = activation
|
||||
|
||||
norm1 = layer_norm_cls(d_model, eps=layer_norm_eps, **factory_kwargs)
|
||||
if layer_norm_cls == IdentityNorm:
|
||||
norm2 = BalancedBasicNorm(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
else:
|
||||
norm2 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
|
||||
if adaptive_layer_norm:
|
||||
self.norm1 = AdaptiveLayerNorm(d_model, norm1)
|
||||
self.norm2 = AdaptiveLayerNorm(d_model, norm2)
|
||||
else:
|
||||
self.norm1 = norm1
|
||||
self.norm2 = norm2
|
||||
|
||||
def __setstate__(self, state):
|
||||
super(TransformerEncoderLayer, self).__setstate__(state)
|
||||
if not hasattr(self, "activation"):
|
||||
self.activation = F.relu
|
||||
|
||||
def forward(
|
||||
self,
|
||||
src: Tensor,
|
||||
src_mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
) -> Tensor:
|
||||
r"""Pass the input through the encoder layer.
|
||||
|
||||
Args:
|
||||
src: the sequence to the encoder layer (required).
|
||||
src_mask: the mask for the src sequence (optional).
|
||||
src_key_padding_mask: the mask for the src keys per batch (optional).
|
||||
|
||||
Shape:
|
||||
see the docs in Transformer class.
|
||||
"""
|
||||
x, stage_embedding = src, None
|
||||
is_src_tuple = False
|
||||
if isinstance(src, tuple):
|
||||
x, stage_embedding = src
|
||||
is_src_tuple = True
|
||||
|
||||
if src_key_padding_mask is not None:
|
||||
_skpm_dtype = src_key_padding_mask.dtype
|
||||
if _skpm_dtype != torch.bool and not torch.is_floating_point(
|
||||
src_key_padding_mask
|
||||
):
|
||||
raise AssertionError(
|
||||
"only bool and floating types of key_padding_mask are supported"
|
||||
)
|
||||
|
||||
if self.norm_first:
|
||||
x = x + self._sa_block(
|
||||
self.norm1(x, stage_embedding),
|
||||
src_mask,
|
||||
src_key_padding_mask,
|
||||
)
|
||||
x = x + self._ff_block(self.norm2(x, stage_embedding))
|
||||
else:
|
||||
x = self.norm1(
|
||||
x + self._sa_block(x, src_mask, src_key_padding_mask),
|
||||
stage_embedding,
|
||||
)
|
||||
x = self.norm2(x + self._ff_block(x), stage_embedding)
|
||||
|
||||
if is_src_tuple:
|
||||
return (x, stage_embedding)
|
||||
return x
|
||||
|
||||
def infer(
|
||||
self,
|
||||
src: Tensor,
|
||||
src_mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
past_kv: Optional[Tensor] = None,
|
||||
use_cache: bool = False,
|
||||
):
|
||||
x, stage_embedding = src, None
|
||||
is_src_tuple = False
|
||||
if isinstance(src, tuple):
|
||||
x, stage_embedding = src
|
||||
is_src_tuple = True
|
||||
|
||||
if src_key_padding_mask is not None:
|
||||
_skpm_dtype = src_key_padding_mask.dtype
|
||||
if _skpm_dtype != torch.bool and not torch.is_floating_point(
|
||||
src_key_padding_mask
|
||||
):
|
||||
raise AssertionError(
|
||||
"only bool and floating types of key_padding_mask are supported"
|
||||
)
|
||||
|
||||
if self.norm_first:
|
||||
x_attn_out, kv = self.self_attn.infer(
|
||||
self.norm1(x, stage_embedding),
|
||||
attn_mask=src_mask,
|
||||
key_padding_mask=src_key_padding_mask,
|
||||
need_weights=False,
|
||||
past_kv=past_kv,
|
||||
use_cache=use_cache,
|
||||
)
|
||||
x = x + x_attn_out
|
||||
x = x + self._ff_block(self.norm2(x, stage_embedding))
|
||||
|
||||
if is_src_tuple:
|
||||
return (x, stage_embedding)
|
||||
return (x, kv)
|
||||
|
||||
# self-attention block
|
||||
def _sa_block(
|
||||
self,
|
||||
x: Tensor,
|
||||
attn_mask: Optional[Tensor],
|
||||
key_padding_mask: Optional[Tensor],
|
||||
) -> Tensor:
|
||||
x = self.self_attn(
|
||||
x,
|
||||
x,
|
||||
x,
|
||||
attn_mask=attn_mask,
|
||||
key_padding_mask=key_padding_mask,
|
||||
need_weights=False,
|
||||
)[0]
|
||||
return self.dropout1(x)
|
||||
|
||||
# feed forward block
|
||||
def _ff_block(self, x: Tensor) -> Tensor:
|
||||
x = self.linear2(self.dropout(self.activation(self.linear1(x))))
|
||||
return self.dropout2(x)
|
||||
|
||||
|
||||
class TransformerEncoder(nn.Module):
|
||||
r"""TransformerEncoder is a stack of N encoder layers. Users can build the
|
||||
BERT(https://arxiv.org/abs/1810.04805) model with corresponding parameters.
|
||||
|
||||
Args:
|
||||
encoder_layer: an instance of the TransformerEncoderLayer() class (required).
|
||||
num_layers: the number of sub-encoder-layers in the encoder (required).
|
||||
norm: the layer normalization component (optional).
|
||||
enable_nested_tensor: if True, input will automatically convert to nested tensor
|
||||
(and convert back on output). This will improve the overall performance of
|
||||
TransformerEncoder when padding rate is high. Default: ``True`` (enabled).
|
||||
|
||||
Examples::
|
||||
>>> encoder_layer = TransformerEncoderLayer(d_model=512, nhead=8)
|
||||
>>> transformer_encoder = TransformerEncoder(encoder_layer, num_layers=6)
|
||||
>>> src = torch.rand(10, 32, 512)
|
||||
>>> out = transformer_encoder(src)
|
||||
"""
|
||||
__constants__ = ["norm"]
|
||||
|
||||
def __init__(self, encoder_layer, num_layers, norm=None):
|
||||
super(TransformerEncoder, self).__init__()
|
||||
self.layers = _get_clones(encoder_layer, num_layers)
|
||||
self.num_layers = num_layers
|
||||
self.norm = norm
|
||||
|
||||
def forward(
|
||||
self,
|
||||
src: Tensor,
|
||||
mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
return_layer_states: bool = False,
|
||||
) -> Tensor:
|
||||
r"""Pass the input through the encoder layers in turn.
|
||||
|
||||
Args:
|
||||
src: the sequence to the encoder (required).
|
||||
mask: the mask for the src sequence (optional).
|
||||
src_key_padding_mask: the mask for the src keys per batch (optional).
|
||||
return_layer_states: return layers' state (optional).
|
||||
|
||||
Shape:
|
||||
see the docs in Transformer class.
|
||||
"""
|
||||
if return_layer_states:
|
||||
layer_states = [] # layers' output
|
||||
output = src
|
||||
for mod in self.layers:
|
||||
output = mod(
|
||||
output,
|
||||
src_mask=mask,
|
||||
src_key_padding_mask=src_key_padding_mask,
|
||||
)
|
||||
layer_states.append(output[0])
|
||||
|
||||
if self.norm is not None:
|
||||
output = self.norm(output)
|
||||
|
||||
return layer_states, output
|
||||
|
||||
output = src
|
||||
for mod in self.layers:
|
||||
output = mod(
|
||||
output, src_mask=mask, src_key_padding_mask=src_key_padding_mask
|
||||
)
|
||||
|
||||
if self.norm is not None:
|
||||
output = self.norm(output)
|
||||
|
||||
return output
|
||||
|
||||
def infer(
|
||||
self,
|
||||
src: Tensor,
|
||||
mask: Optional[Tensor] = None,
|
||||
src_key_padding_mask: Optional[Tensor] = None,
|
||||
return_layer_states: bool = False,
|
||||
past_kv: Optional[Tensor] = None,
|
||||
use_cache: bool = False,
|
||||
):
|
||||
if past_kv is None:
|
||||
past_length = 0
|
||||
past_kv = tuple([None] * self.num_layers)
|
||||
else:
|
||||
past_length = past_kv[0][0].size(-2)
|
||||
new_kv = () if use_cache else None
|
||||
output = src
|
||||
for mod, past_layer_kv in zip(self.layers, past_kv):
|
||||
output, kv = mod.infer(
|
||||
output, src_mask=mask, src_key_padding_mask=src_key_padding_mask, past_kv=past_layer_kv, use_cache=use_cache
|
||||
)
|
||||
if use_cache:
|
||||
new_kv = new_kv + (kv,)
|
||||
|
||||
if self.norm is not None:
|
||||
output = self.norm(output)
|
||||
|
||||
return output, new_kv
|
||||
|
||||
|
||||
class TransformerDecoderLayer(nn.Module):
|
||||
__constants__ = ["batch_first", "norm_first"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int,
|
||||
nhead: int,
|
||||
dim_feedforward: int = 2048,
|
||||
dropout: float = 0.1,
|
||||
activation: Union[str, Callable[[Tensor], Tensor]] = F.relu,
|
||||
linear1_self_attention_cls: nn.Module = nn.Linear,
|
||||
linear2_self_attention_cls: nn.Module = nn.Linear,
|
||||
linear1_feedforward_cls: nn.Module = nn.Linear,
|
||||
linear2_feedforward_cls: nn.Module = nn.Linear,
|
||||
batch_first: bool = False,
|
||||
norm_first: bool = False,
|
||||
device=None,
|
||||
dtype=None,
|
||||
layer_norm_cls: nn.Module = LayerNorm,
|
||||
layer_norm_eps: float = 1e-5,
|
||||
adaptive_layer_norm=False,
|
||||
) -> None:
|
||||
factory_kwargs = {"device": device, "dtype": dtype}
|
||||
super(TransformerDecoderLayer, self).__init__()
|
||||
self.self_attn = MultiheadAttention(
|
||||
d_model,
|
||||
nhead,
|
||||
dropout=dropout,
|
||||
batch_first=batch_first,
|
||||
linear1_cls=linear1_self_attention_cls,
|
||||
linear2_cls=linear2_self_attention_cls,
|
||||
**factory_kwargs,
|
||||
)
|
||||
self.multihead_attn = MultiheadAttention(
|
||||
d_model,
|
||||
nhead,
|
||||
dropout=dropout,
|
||||
batch_first=batch_first,
|
||||
linear1_cls=linear1_self_attention_cls,
|
||||
linear2_cls=linear2_self_attention_cls,
|
||||
**factory_kwargs,
|
||||
)
|
||||
# Implementation of Feedforward model
|
||||
self.linear1 = linear1_feedforward_cls(
|
||||
d_model, dim_feedforward, **factory_kwargs
|
||||
)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.linear2 = linear2_feedforward_cls(
|
||||
dim_feedforward, d_model, **factory_kwargs
|
||||
)
|
||||
|
||||
self.norm_first = norm_first
|
||||
self.dropout1 = nn.Dropout(dropout)
|
||||
self.dropout2 = nn.Dropout(dropout)
|
||||
self.dropout3 = nn.Dropout(dropout)
|
||||
|
||||
# Legacy string support for activation function.
|
||||
if isinstance(activation, str):
|
||||
self.activation = _get_activation_fn(activation)
|
||||
elif isinstance(activation, partial):
|
||||
self.activation = activation(d_model)
|
||||
elif activation == BalancedDoubleSwish:
|
||||
self.activation = BalancedDoubleSwish(d_model)
|
||||
else:
|
||||
self.activation = activation
|
||||
|
||||
if adaptive_layer_norm:
|
||||
norm1 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
norm2 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
norm3 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
|
||||
self.norm1 = AdaptiveLayerNorm(d_model, norm1)
|
||||
self.norm2 = AdaptiveLayerNorm(d_model, norm2)
|
||||
self.norm3 = AdaptiveLayerNorm(d_model, norm3)
|
||||
else:
|
||||
self.norm1 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
self.norm2 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
if layer_norm_cls == IdentityNorm:
|
||||
self.norm3 = BalancedBasicNorm(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
else:
|
||||
self.norm3 = layer_norm_cls(
|
||||
d_model, eps=layer_norm_eps, **factory_kwargs
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
tgt: Tensor,
|
||||
memory: Tensor,
|
||||
tgt_mask: Optional[Tensor] = None,
|
||||
memory_mask: Optional[Tensor] = None,
|
||||
tgt_key_padding_mask: Optional[Tensor] = None,
|
||||
memory_key_padding_mask: Optional[Tensor] = None,
|
||||
) -> Tensor:
|
||||
r"""Pass the inputs (and mask) through the decoder layer.
|
||||
|
||||
Args:
|
||||
tgt: the sequence to the decoder layer (required).
|
||||
memory: the sequence from the last layer of the encoder (required).
|
||||
tgt_mask: the mask for the tgt sequence (optional).
|
||||
memory_mask: the mask for the memory sequence (optional).
|
||||
tgt_key_padding_mask: the mask for the tgt keys per batch (optional).
|
||||
memory_key_padding_mask: the mask for the memory keys per batch (optional).
|
||||
|
||||
Shape:
|
||||
see the docs in Transformer class.
|
||||
"""
|
||||
tgt_is_tuple = False
|
||||
if isinstance(tgt, tuple):
|
||||
x, stage_embedding = tgt
|
||||
tgt_is_tuple = True
|
||||
else:
|
||||
x, stage_embedding = tgt, None
|
||||
|
||||
if self.norm_first:
|
||||
x = x + self._sa_block(
|
||||
self.norm1(x, stage_embedding), tgt_mask, tgt_key_padding_mask
|
||||
)
|
||||
x = x + self._mha_block(
|
||||
self.norm2(x, stage_embedding),
|
||||
memory,
|
||||
memory_mask,
|
||||
memory_key_padding_mask,
|
||||
)
|
||||
x = x + self._ff_block(self.norm3(x, stage_embedding))
|
||||
else:
|
||||
x = self.norm1(
|
||||
x + self._sa_block(x, tgt_mask, tgt_key_padding_mask),
|
||||
stage_embedding,
|
||||
)
|
||||
x = self.norm2(
|
||||
x
|
||||
+ self._mha_block(
|
||||
x, memory, memory_mask, memory_key_padding_mask
|
||||
),
|
||||
stage_embedding,
|
||||
)
|
||||
x = self.norm3(x + self._ff_block(x), stage_embedding)
|
||||
|
||||
if tgt_is_tuple:
|
||||
return (x, stage_embedding)
|
||||
return x
|
||||
|
||||
# self-attention block
|
||||
def _sa_block(
|
||||
self,
|
||||
x: Tensor,
|
||||
attn_mask: Optional[Tensor],
|
||||
key_padding_mask: Optional[Tensor],
|
||||
) -> Tensor:
|
||||
x = self.self_attn(
|
||||
x,
|
||||
x,
|
||||
x,
|
||||
attn_mask=attn_mask,
|
||||
key_padding_mask=key_padding_mask,
|
||||
need_weights=False,
|
||||
)[0]
|
||||
return self.dropout1(x)
|
||||
|
||||
# multihead attention block
|
||||
def _mha_block(
|
||||
self,
|
||||
x: Tensor,
|
||||
mem: Tensor,
|
||||
attn_mask: Optional[Tensor],
|
||||
key_padding_mask: Optional[Tensor],
|
||||
) -> Tensor:
|
||||
x = self.multihead_attn(
|
||||
x,
|
||||
mem,
|
||||
mem,
|
||||
attn_mask=attn_mask,
|
||||
key_padding_mask=key_padding_mask,
|
||||
need_weights=False,
|
||||
)[0]
|
||||
return self.dropout2(x)
|
||||
|
||||
# feed forward block
|
||||
def _ff_block(self, x: Tensor) -> Tensor:
|
||||
x = self.linear2(self.dropout(self.activation(self.linear1(x))))
|
||||
return self.dropout3(x)
|
||||
|
||||
|
||||
def _get_clones(module, N):
|
||||
return nn.ModuleList([copy.deepcopy(module) for i in range(N)])
|
||||
|
||||
|
||||
def _get_activation_fn(activation: str) -> Callable[[Tensor], Tensor]:
|
||||
if activation == "relu":
|
||||
return F.relu
|
||||
elif activation == "gelu":
|
||||
return F.gelu
|
||||
|
||||
raise RuntimeError(
|
||||
"activation should be relu/gelu, not {}".format(activation)
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
Pretrained Punkt Models -- Jan Strunk (New version trained after issues 313 and 514 had been corrected)
|
||||
|
||||
Most models were prepared using the test corpora from Kiss and Strunk (2006). Additional models have
|
||||
been contributed by various people using NLTK for sentence boundary detection.
|
||||
|
||||
For information about how to use these models, please confer the tokenization HOWTO:
|
||||
http://nltk.googlecode.com/svn/trunk/doc/howto/tokenize.html
|
||||
and chapter 3.8 of the NLTK book:
|
||||
http://nltk.googlecode.com/svn/trunk/doc/book/ch03.html#sec-segmentation
|
||||
|
||||
There are pretrained tokenizers for the following languages:
|
||||
|
||||
File Language Source Contents Size of training corpus(in tokens) Model contributed by
|
||||
=======================================================================================================================================================================
|
||||
czech.pickle Czech Multilingual Corpus 1 (ECI) Lidove Noviny ~345,000 Jan Strunk / Tibor Kiss
|
||||
Literarni Noviny
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
danish.pickle Danish Avisdata CD-Rom Ver. 1.1. 1995 Berlingske Tidende ~550,000 Jan Strunk / Tibor Kiss
|
||||
(Berlingske Avisdata, Copenhagen) Weekend Avisen
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
dutch.pickle Dutch Multilingual Corpus 1 (ECI) De Limburger ~340,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
english.pickle English Penn Treebank (LDC) Wall Street Journal ~469,000 Jan Strunk / Tibor Kiss
|
||||
(American)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
estonian.pickle Estonian University of Tartu, Estonia Eesti Ekspress ~359,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
finnish.pickle Finnish Finnish Parole Corpus, Finnish Books and major national ~364,000 Jan Strunk / Tibor Kiss
|
||||
Text Bank (Suomen Kielen newspapers
|
||||
Tekstipankki)
|
||||
Finnish Center for IT Science
|
||||
(CSC)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
french.pickle French Multilingual Corpus 1 (ECI) Le Monde ~370,000 Jan Strunk / Tibor Kiss
|
||||
(European)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
german.pickle German Neue Zürcher Zeitung AG Neue Zürcher Zeitung ~847,000 Jan Strunk / Tibor Kiss
|
||||
(Switzerland) CD-ROM
|
||||
(Uses "ss"
|
||||
instead of "ß")
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
greek.pickle Greek Efstathios Stamatatos To Vima (TO BHMA) ~227,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
italian.pickle Italian Multilingual Corpus 1 (ECI) La Stampa, Il Mattino ~312,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
norwegian.pickle Norwegian Centre for Humanities Bergens Tidende ~479,000 Jan Strunk / Tibor Kiss
|
||||
(Bokmål and Information Technologies,
|
||||
Nynorsk) Bergen
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
polish.pickle Polish Polish National Corpus Literature, newspapers, etc. ~1,000,000 Krzysztof Langner
|
||||
(http://www.nkjp.pl/)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
portuguese.pickle Portuguese CETENFolha Corpus Folha de São Paulo ~321,000 Jan Strunk / Tibor Kiss
|
||||
(Brazilian) (Linguateca)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
slovene.pickle Slovene TRACTOR Delo ~354,000 Jan Strunk / Tibor Kiss
|
||||
Slovene Academy for Arts
|
||||
and Sciences
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
spanish.pickle Spanish Multilingual Corpus 1 (ECI) Sur ~353,000 Jan Strunk / Tibor Kiss
|
||||
(European)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
swedish.pickle Swedish Multilingual Corpus 1 (ECI) Dagens Nyheter ~339,000 Jan Strunk / Tibor Kiss
|
||||
(and some other texts)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
turkish.pickle Turkish METU Turkish Corpus Milliyet ~333,000 Jan Strunk / Tibor Kiss
|
||||
(Türkçe Derlem Projesi)
|
||||
University of Ankara
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
The corpora contained about 400,000 tokens on average and mostly consisted of newspaper text converted to
|
||||
Unicode using the codecs module.
|
||||
|
||||
Kiss, Tibor and Strunk, Jan (2006): Unsupervised Multilingual Sentence Boundary Detection.
|
||||
Computational Linguistics 32: 485-525.
|
||||
|
||||
---- Training Code ----
|
||||
|
||||
# import punkt
|
||||
import nltk.tokenize.punkt
|
||||
|
||||
# Make a new Tokenizer
|
||||
tokenizer = nltk.tokenize.punkt.PunktSentenceTokenizer()
|
||||
|
||||
# Read in training corpus (one example: Slovene)
|
||||
import codecs
|
||||
text = codecs.open("slovene.plain","Ur","iso-8859-2").read()
|
||||
|
||||
# Train tokenizer
|
||||
tokenizer.train(text)
|
||||
|
||||
# Dump pickled tokenizer
|
||||
import pickle
|
||||
out = open("slovene.pickle","wb")
|
||||
pickle.dump(tokenizer, out)
|
||||
out.close()
|
||||
|
||||
---------
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
Pretrained Punkt Models -- Jan Strunk (New version trained after issues 313 and 514 had been corrected)
|
||||
|
||||
Most models were prepared using the test corpora from Kiss and Strunk (2006). Additional models have
|
||||
been contributed by various people using NLTK for sentence boundary detection.
|
||||
|
||||
For information about how to use these models, please confer the tokenization HOWTO:
|
||||
http://nltk.googlecode.com/svn/trunk/doc/howto/tokenize.html
|
||||
and chapter 3.8 of the NLTK book:
|
||||
http://nltk.googlecode.com/svn/trunk/doc/book/ch03.html#sec-segmentation
|
||||
|
||||
There are pretrained tokenizers for the following languages:
|
||||
|
||||
File Language Source Contents Size of training corpus(in tokens) Model contributed by
|
||||
=======================================================================================================================================================================
|
||||
czech.pickle Czech Multilingual Corpus 1 (ECI) Lidove Noviny ~345,000 Jan Strunk / Tibor Kiss
|
||||
Literarni Noviny
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
danish.pickle Danish Avisdata CD-Rom Ver. 1.1. 1995 Berlingske Tidende ~550,000 Jan Strunk / Tibor Kiss
|
||||
(Berlingske Avisdata, Copenhagen) Weekend Avisen
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
dutch.pickle Dutch Multilingual Corpus 1 (ECI) De Limburger ~340,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
english.pickle English Penn Treebank (LDC) Wall Street Journal ~469,000 Jan Strunk / Tibor Kiss
|
||||
(American)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
estonian.pickle Estonian University of Tartu, Estonia Eesti Ekspress ~359,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
finnish.pickle Finnish Finnish Parole Corpus, Finnish Books and major national ~364,000 Jan Strunk / Tibor Kiss
|
||||
Text Bank (Suomen Kielen newspapers
|
||||
Tekstipankki)
|
||||
Finnish Center for IT Science
|
||||
(CSC)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
french.pickle French Multilingual Corpus 1 (ECI) Le Monde ~370,000 Jan Strunk / Tibor Kiss
|
||||
(European)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
german.pickle German Neue Zürcher Zeitung AG Neue Zürcher Zeitung ~847,000 Jan Strunk / Tibor Kiss
|
||||
(Switzerland) CD-ROM
|
||||
(Uses "ss"
|
||||
instead of "ß")
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
greek.pickle Greek Efstathios Stamatatos To Vima (TO BHMA) ~227,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
italian.pickle Italian Multilingual Corpus 1 (ECI) La Stampa, Il Mattino ~312,000 Jan Strunk / Tibor Kiss
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
norwegian.pickle Norwegian Centre for Humanities Bergens Tidende ~479,000 Jan Strunk / Tibor Kiss
|
||||
(Bokmål and Information Technologies,
|
||||
Nynorsk) Bergen
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
polish.pickle Polish Polish National Corpus Literature, newspapers, etc. ~1,000,000 Krzysztof Langner
|
||||
(http://www.nkjp.pl/)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
portuguese.pickle Portuguese CETENFolha Corpus Folha de São Paulo ~321,000 Jan Strunk / Tibor Kiss
|
||||
(Brazilian) (Linguateca)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
slovene.pickle Slovene TRACTOR Delo ~354,000 Jan Strunk / Tibor Kiss
|
||||
Slovene Academy for Arts
|
||||
and Sciences
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
spanish.pickle Spanish Multilingual Corpus 1 (ECI) Sur ~353,000 Jan Strunk / Tibor Kiss
|
||||
(European)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
swedish.pickle Swedish Multilingual Corpus 1 (ECI) Dagens Nyheter ~339,000 Jan Strunk / Tibor Kiss
|
||||
(and some other texts)
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
turkish.pickle Turkish METU Turkish Corpus Milliyet ~333,000 Jan Strunk / Tibor Kiss
|
||||
(Türkçe Derlem Projesi)
|
||||
University of Ankara
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
The corpora contained about 400,000 tokens on average and mostly consisted of newspaper text converted to
|
||||
Unicode using the codecs module.
|
||||
|
||||
Kiss, Tibor and Strunk, Jan (2006): Unsupervised Multilingual Sentence Boundary Detection.
|
||||
Computational Linguistics 32: 485-525.
|
||||
|
||||
---- Training Code ----
|
||||
|
||||
# import punkt
|
||||
import nltk.tokenize.punkt
|
||||
|
||||
# Make a new Tokenizer
|
||||
tokenizer = nltk.tokenize.punkt.PunktSentenceTokenizer()
|
||||
|
||||
# Read in training corpus (one example: Slovene)
|
||||
import codecs
|
||||
text = codecs.open("slovene.plain","Ur","iso-8859-2").read()
|
||||
|
||||
# Train tokenizer
|
||||
tokenizer.train(text)
|
||||
|
||||
# Dump pickled tokenizer
|
||||
import pickle
|
||||
out = open("slovene.pickle","wb")
|
||||
pickle.dump(tokenizer, out)
|
||||
out.close()
|
||||
|
||||
---------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user