Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- .gitignore +0 -1
- MP-SENet/LICENSE +21 -0
- MP-SENet/README.md +66 -0
- MP-SENet/best_ckpt/config.json +28 -0
- MP-SENet/best_ckpt/g_best_dns +3 -0
- MP-SENet/dataset.py +95 -0
- MP-SENet/env.py +14 -0
- MP-SENet/models/model.py +195 -0
- MP-SENet/models/transformer.py +66 -0
- MP-SENet/utils.py +99 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
MP-SENet/best_ckpt/g_best_dns filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
|
@@ -23,7 +23,6 @@ build/
|
|
| 23 |
*.swo
|
| 24 |
|
| 25 |
# Project-specific
|
| 26 |
-
MP-SENet/
|
| 27 |
*.wav
|
| 28 |
*.mp3
|
| 29 |
*.flac
|
|
|
|
| 23 |
*.swo
|
| 24 |
|
| 25 |
# Project-specific
|
|
|
|
| 26 |
*.wav
|
| 27 |
*.mp3
|
| 28 |
*.flac
|
MP-SENet/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Yexin Lu
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
MP-SENet/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Explicit Estimation of Magnitude and Phase Spectra in Parallel for High-Quality Speech Enhancement
|
| 2 |
+
### Ye-Xin Lu, Yang Ai, Zhen-Hua Ling
|
| 3 |
+
In our [paper](https://arxiv.org/abs/2305.13686), we proposed MP-SENet: a TF-domain monaural SE model with parallel magnitude and phase spectra denoising.<br>
|
| 4 |
+
A [long-version](https://arxiv.org/abs/2308.08926) MP-SENet was extended to the speech denoising, dereverberation, and bandwidth extension tasks.<br>
|
| 5 |
+
Audio samples can be found at the [demo website](http://yxlu-0102.github.io/MP-SENet).<br>
|
| 6 |
+
We provide our implementation as open source in this repository.
|
| 7 |
+
|
| 8 |
+
## ⚠️ Note
|
| 9 |
+
There is a small bug in our code, but it does not affect the overall performance of the model.
|
| 10 |
+
If you intend to retrain the model, it’s **strongly recommended** to set `batch_first=True` in the `MultiHeadAttention` module inside [transformer.py](models/transformer.py), which can significantly reduce the memory usage of the model.
|
| 11 |
+
|
| 12 |
+
## Pre-requisites
|
| 13 |
+
1. Python >= 3.6.
|
| 14 |
+
2. Clone this repository.
|
| 15 |
+
3. Install python requirements. Please refer [requirements.txt](https://github.com/yxlu-0102/MP-SENet/blob/main/requirements.txt).
|
| 16 |
+
4. Download and extract the [VoiceBank+DEMAND dataset](https://datashare.ed.ac.uk/handle/10283/1942). Resample all wav files to 16kHz, and move the clean and noisy wavs to `VoiceBank+DEMAND/wavs_clean` and `VoiceBank+DEMAND/wavs_noisy`, respectively. You can also directly download the downsampled 16kHz dataset [here](https://drive.google.com/drive/folders/19I_thf6F396y5gZxLTxYIojZXC0Ywm8l).
|
| 17 |
+
|
| 18 |
+
## Training
|
| 19 |
+
```
|
| 20 |
+
CUDA_VISIBLE_DEVICES=0,1 python train.py --config config.json
|
| 21 |
+
```
|
| 22 |
+
Checkpoints and copy of the configuration file are saved in the `cp_mpsenet` directory by default.<br>
|
| 23 |
+
You can change the path by adding `--checkpoint_path` option.
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
```
|
| 27 |
+
python inference.py --checkpoint_file [generator checkpoint file path]
|
| 28 |
+
```
|
| 29 |
+
You can also use the pretrained best checkpoint files we provide in the `best_ckpt` directory.
|
| 30 |
+
<br>
|
| 31 |
+
Generated wav files are saved in `generated_files` by default.
|
| 32 |
+
You can change the path by adding `--output_dir` option.<br>
|
| 33 |
+
Here is an example:
|
| 34 |
+
```
|
| 35 |
+
python inference.py --checkpoint_file best_ckpt/g_best_vb --output_dir generated_files/MP-SENet_VB
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## Model Structure
|
| 39 |
+

|
| 40 |
+
|
| 41 |
+
## Comparison with other SE models
|
| 42 |
+

|
| 43 |
+
|
| 44 |
+
## Acknowledgements
|
| 45 |
+
We referred to [HiFiGAN](https://github.com/jik876/hifi-gan), [NSPP](https://github.com/YangAi520/NSPP)
|
| 46 |
+
and [CMGAN](https://github.com/ruizhecao96/CMGAN) to implement this.
|
| 47 |
+
|
| 48 |
+
## Citation
|
| 49 |
+
```
|
| 50 |
+
@inproceedings{lu2023mp,
|
| 51 |
+
title={{MP-SENet}: A Speech Enhancement Model with Parallel Denoising of Magnitude and Phase Spectra},
|
| 52 |
+
author={Lu, Ye-Xin and Ai, Yang and Ling, Zhen-Hua},
|
| 53 |
+
booktitle={Proc. Interspeech},
|
| 54 |
+
pages={3834--3838},
|
| 55 |
+
year={2023}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
@article{lu2023explicit,
|
| 59 |
+
title={Explicit estimation of magnitude and phase spectra in parallel for high-quality speech enhancement},
|
| 60 |
+
author={Lu, Ye-Xin and Ai, Yang and Ling, Zhen-Hua},
|
| 61 |
+
journal={Neural Networks},
|
| 62 |
+
volume = {189},
|
| 63 |
+
pages = {107562},
|
| 64 |
+
year={2025}
|
| 65 |
+
}
|
| 66 |
+
```
|
MP-SENet/best_ckpt/config.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"num_gpus": 0,
|
| 3 |
+
"batch_size": 4,
|
| 4 |
+
"learning_rate": 0.0005,
|
| 5 |
+
"adam_b1": 0.8,
|
| 6 |
+
"adam_b2": 0.99,
|
| 7 |
+
"lr_decay": 0.99,
|
| 8 |
+
"seed": 1234,
|
| 9 |
+
|
| 10 |
+
"dense_channel": 64,
|
| 11 |
+
"compress_factor": 0.3,
|
| 12 |
+
"num_tsconformers": 4,
|
| 13 |
+
"beta": 2.0,
|
| 14 |
+
|
| 15 |
+
"sampling_rate": 16000,
|
| 16 |
+
"segment_size": 32000,
|
| 17 |
+
"n_fft": 400,
|
| 18 |
+
"hop_size": 100,
|
| 19 |
+
"win_size": 400,
|
| 20 |
+
|
| 21 |
+
"num_workers": 4,
|
| 22 |
+
|
| 23 |
+
"dist_config": {
|
| 24 |
+
"dist_backend": "nccl",
|
| 25 |
+
"dist_url": "tcp://localhost:54321",
|
| 26 |
+
"world_size": 1
|
| 27 |
+
}
|
| 28 |
+
}
|
MP-SENet/best_ckpt/g_best_dns
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:97a77ba67c5c484c65363bb703ea85962f773ca0819e22ce81b4ec33db5e7206
|
| 3 |
+
size 9138054
|
MP-SENet/dataset.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
import torch
|
| 4 |
+
import torch.utils.data
|
| 5 |
+
import librosa
|
| 6 |
+
|
| 7 |
+
def mag_pha_stft(y, n_fft, hop_size, win_size, compress_factor=1.0, center=True):
|
| 8 |
+
|
| 9 |
+
hann_window = torch.hann_window(win_size).to(y.device)
|
| 10 |
+
stft_spec = torch.stft(y, n_fft, hop_length=hop_size, win_length=win_size, window=hann_window,
|
| 11 |
+
center=center, pad_mode='reflect', normalized=False, return_complex=True)
|
| 12 |
+
stft_spec = torch.view_as_real(stft_spec)
|
| 13 |
+
mag = torch.sqrt(stft_spec.pow(2).sum(-1)+(1e-9))
|
| 14 |
+
pha = torch.atan2(stft_spec[:, :, :, 1]+(1e-10), stft_spec[:, :, :, 0]+(1e-5))
|
| 15 |
+
# Magnitude Compression
|
| 16 |
+
mag = torch.pow(mag, compress_factor)
|
| 17 |
+
com = torch.stack((mag*torch.cos(pha), mag*torch.sin(pha)), dim=-1)
|
| 18 |
+
|
| 19 |
+
return mag, pha, com
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def mag_pha_istft(mag, pha, n_fft, hop_size, win_size, compress_factor=1.0, center=True):
|
| 23 |
+
# Magnitude Decompression
|
| 24 |
+
mag = torch.pow(mag, (1.0/compress_factor))
|
| 25 |
+
com = torch.complex(mag*torch.cos(pha), mag*torch.sin(pha))
|
| 26 |
+
hann_window = torch.hann_window(win_size).to(com.device)
|
| 27 |
+
wav = torch.istft(com, n_fft, hop_length=hop_size, win_length=win_size, window=hann_window, center=center)
|
| 28 |
+
|
| 29 |
+
return wav
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def get_dataset_filelist(a):
|
| 33 |
+
with open(a.input_training_file, 'r', encoding='utf-8') as fi:
|
| 34 |
+
training_indexes = [x.split('|')[0] for x in fi.read().split('\n') if len(x) > 0]
|
| 35 |
+
|
| 36 |
+
with open(a.input_validation_file, 'r', encoding='utf-8') as fi:
|
| 37 |
+
validation_indexes = [x.split('|')[0] for x in fi.read().split('\n') if len(x) > 0]
|
| 38 |
+
|
| 39 |
+
return training_indexes, validation_indexes
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class Dataset(torch.utils.data.Dataset):
|
| 43 |
+
def __init__(self, training_indexes, clean_wavs_dir, noisy_wavs_dir, segment_size,
|
| 44 |
+
sampling_rate, split=True, shuffle=True, n_cache_reuse=1, device=None):
|
| 45 |
+
self.audio_indexes = training_indexes
|
| 46 |
+
random.seed(1234)
|
| 47 |
+
if shuffle:
|
| 48 |
+
random.shuffle(self.audio_indexes)
|
| 49 |
+
self.clean_wavs_dir = clean_wavs_dir
|
| 50 |
+
self.noisy_wavs_dir = noisy_wavs_dir
|
| 51 |
+
self.segment_size = segment_size
|
| 52 |
+
self.sampling_rate = sampling_rate
|
| 53 |
+
self.split = split
|
| 54 |
+
self.cached_clean_wav = None
|
| 55 |
+
self.cached_noisy_wav = None
|
| 56 |
+
self.n_cache_reuse = n_cache_reuse
|
| 57 |
+
self._cache_ref_count = 0
|
| 58 |
+
self.device = device
|
| 59 |
+
|
| 60 |
+
def __getitem__(self, index):
|
| 61 |
+
filename = self.audio_indexes[index]
|
| 62 |
+
if self._cache_ref_count == 0:
|
| 63 |
+
clean_audio, _ = librosa.load(os.path.join(self.clean_wavs_dir, filename + '.wav'), sr=self.sampling_rate)
|
| 64 |
+
noisy_audio, _ = librosa.load(os.path.join(self.noisy_wavs_dir, filename + '.wav'), sr=self.sampling_rate)
|
| 65 |
+
length = min(len(clean_audio), len(noisy_audio))
|
| 66 |
+
clean_audio, noisy_audio = clean_audio[: length], noisy_audio[: length]
|
| 67 |
+
self.cached_clean_wav = clean_audio
|
| 68 |
+
self.cached_noisy_wav = noisy_audio
|
| 69 |
+
self._cache_ref_count = self.n_cache_reuse
|
| 70 |
+
else:
|
| 71 |
+
clean_audio = self.cached_clean_wav
|
| 72 |
+
noisy_audio = self.cached_noisy_wav
|
| 73 |
+
self._cache_ref_count -= 1
|
| 74 |
+
|
| 75 |
+
clean_audio, noisy_audio = torch.FloatTensor(clean_audio), torch.FloatTensor(noisy_audio)
|
| 76 |
+
norm_factor = torch.sqrt(len(noisy_audio) / torch.sum(noisy_audio ** 2.0))
|
| 77 |
+
clean_audio = (clean_audio * norm_factor).unsqueeze(0)
|
| 78 |
+
noisy_audio = (noisy_audio * norm_factor).unsqueeze(0)
|
| 79 |
+
|
| 80 |
+
assert clean_audio.size(1) == noisy_audio.size(1)
|
| 81 |
+
|
| 82 |
+
if self.split:
|
| 83 |
+
if clean_audio.size(1) >= self.segment_size:
|
| 84 |
+
max_audio_start = clean_audio.size(1) - self.segment_size
|
| 85 |
+
audio_start = random.randint(0, max_audio_start)
|
| 86 |
+
clean_audio = clean_audio[:, audio_start: audio_start+self.segment_size]
|
| 87 |
+
noisy_audio = noisy_audio[:, audio_start: audio_start+self.segment_size]
|
| 88 |
+
else:
|
| 89 |
+
clean_audio = torch.nn.functional.pad(clean_audio, (0, self.segment_size - clean_audio.size(1)), 'constant')
|
| 90 |
+
noisy_audio = torch.nn.functional.pad(noisy_audio, (0, self.segment_size - noisy_audio.size(1)), 'constant')
|
| 91 |
+
|
| 92 |
+
return (clean_audio.squeeze(), noisy_audio.squeeze())
|
| 93 |
+
|
| 94 |
+
def __len__(self):
|
| 95 |
+
return len(self.audio_indexes)
|
MP-SENet/env.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
|
| 4 |
+
class AttrDict(dict):
|
| 5 |
+
def __init__(self, *args, **kwargs):
|
| 6 |
+
super(AttrDict, self).__init__(*args, **kwargs)
|
| 7 |
+
self.__dict__ = self
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def build_env(config, config_name, path):
|
| 11 |
+
t_path = os.path.join(path, config_name)
|
| 12 |
+
if config != t_path:
|
| 13 |
+
os.makedirs(path, exist_ok=True)
|
| 14 |
+
shutil.copyfile(config, os.path.join(path, config_name))
|
MP-SENet/models/model.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import numpy as np
|
| 4 |
+
from models.transformer import TransformerBlock
|
| 5 |
+
from utils import LearnableSigmoid2d
|
| 6 |
+
from pesq import pesq
|
| 7 |
+
from joblib import Parallel, delayed
|
| 8 |
+
|
| 9 |
+
class SPConvTranspose2d(nn.Module):
|
| 10 |
+
def __init__(self, in_channels, out_channels, kernel_size, r=1):
|
| 11 |
+
super(SPConvTranspose2d, self).__init__()
|
| 12 |
+
self.pad1 = nn.ConstantPad2d((1, 1, 0, 0), value=0.)
|
| 13 |
+
self.out_channels = out_channels
|
| 14 |
+
self.conv = nn.Conv2d(in_channels, out_channels * r, kernel_size=kernel_size, stride=(1, 1))
|
| 15 |
+
self.r = r
|
| 16 |
+
|
| 17 |
+
def forward(self, x):
|
| 18 |
+
x = self.pad1(x)
|
| 19 |
+
out = self.conv(x)
|
| 20 |
+
batch_size, nchannels, H, W = out.shape
|
| 21 |
+
out = out.view((batch_size, self.r, nchannels // self.r, H, W))
|
| 22 |
+
out = out.permute(0, 2, 3, 4, 1)
|
| 23 |
+
out = out.contiguous().view((batch_size, nchannels // self.r, H, -1))
|
| 24 |
+
return out
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class DenseBlock(nn.Module):
|
| 28 |
+
def __init__(self, h, kernel_size=(2, 3), depth=4):
|
| 29 |
+
super(DenseBlock, self).__init__()
|
| 30 |
+
self.h = h
|
| 31 |
+
self.depth = depth
|
| 32 |
+
self.dense_block = nn.ModuleList([])
|
| 33 |
+
for i in range(depth):
|
| 34 |
+
dilation = 2 ** i
|
| 35 |
+
pad_length = dilation
|
| 36 |
+
dense_conv = nn.Sequential(
|
| 37 |
+
nn.ConstantPad2d((1, 1, pad_length, 0), value=0.),
|
| 38 |
+
nn.Conv2d(h.dense_channel*(i+1), h.dense_channel, kernel_size, dilation=(dilation, 1)),
|
| 39 |
+
nn.InstanceNorm2d(h.dense_channel, affine=True),
|
| 40 |
+
nn.PReLU(h.dense_channel)
|
| 41 |
+
)
|
| 42 |
+
self.dense_block.append(dense_conv)
|
| 43 |
+
|
| 44 |
+
def forward(self, x):
|
| 45 |
+
skip = x
|
| 46 |
+
for i in range(self.depth):
|
| 47 |
+
x = self.dense_block[i](skip)
|
| 48 |
+
skip = torch.cat([x, skip], dim=1)
|
| 49 |
+
return x
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class DenseEncoder(nn.Module):
|
| 53 |
+
def __init__(self, h, in_channel):
|
| 54 |
+
super(DenseEncoder, self).__init__()
|
| 55 |
+
self.h = h
|
| 56 |
+
self.dense_conv_1 = nn.Sequential(
|
| 57 |
+
nn.Conv2d(in_channel, h.dense_channel, (1, 1)),
|
| 58 |
+
nn.InstanceNorm2d(h.dense_channel, affine=True),
|
| 59 |
+
nn.PReLU(h.dense_channel))
|
| 60 |
+
|
| 61 |
+
self.dense_block = DenseBlock(h, depth=4)
|
| 62 |
+
|
| 63 |
+
self.dense_conv_2 = nn.Sequential(
|
| 64 |
+
nn.Conv2d(h.dense_channel, h.dense_channel, (1, 3), (1, 2), padding=(0, 1)),
|
| 65 |
+
nn.InstanceNorm2d(h.dense_channel, affine=True),
|
| 66 |
+
nn.PReLU(h.dense_channel))
|
| 67 |
+
|
| 68 |
+
def forward(self, x):
|
| 69 |
+
x = self.dense_conv_1(x) # [b, 64, T, F]
|
| 70 |
+
x = self.dense_block(x) # [b, 64, T, F]
|
| 71 |
+
x = self.dense_conv_2(x) # [b, 64, T, F//2]
|
| 72 |
+
return x
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class MaskDecoder(nn.Module):
|
| 76 |
+
def __init__(self, h, out_channel=1):
|
| 77 |
+
super(MaskDecoder, self).__init__()
|
| 78 |
+
self.dense_block = DenseBlock(h, depth=4)
|
| 79 |
+
self.mask_conv = nn.Sequential(
|
| 80 |
+
SPConvTranspose2d(h.dense_channel, h.dense_channel, (1, 3), 2),
|
| 81 |
+
nn.InstanceNorm2d(h.dense_channel, affine=True),
|
| 82 |
+
nn.PReLU(h.dense_channel),
|
| 83 |
+
nn.Conv2d(h.dense_channel, out_channel, (1, 2))
|
| 84 |
+
)
|
| 85 |
+
self.lsigmoid = LearnableSigmoid2d(h.n_fft//2+1, beta=h.beta)
|
| 86 |
+
|
| 87 |
+
def forward(self, x):
|
| 88 |
+
x = self.dense_block(x)
|
| 89 |
+
x = self.mask_conv(x)
|
| 90 |
+
x = x.permute(0, 3, 2, 1).squeeze(-1) # [B, F, T]
|
| 91 |
+
x = self.lsigmoid(x)
|
| 92 |
+
return x
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class PhaseDecoder(nn.Module):
|
| 96 |
+
def __init__(self, h, out_channel=1):
|
| 97 |
+
super(PhaseDecoder, self).__init__()
|
| 98 |
+
self.dense_block = DenseBlock(h, depth=4)
|
| 99 |
+
self.phase_conv = nn.Sequential(
|
| 100 |
+
SPConvTranspose2d(h.dense_channel, h.dense_channel, (1, 3), 2),
|
| 101 |
+
nn.InstanceNorm2d(h.dense_channel, affine=True),
|
| 102 |
+
nn.PReLU(h.dense_channel)
|
| 103 |
+
)
|
| 104 |
+
self.phase_conv_r = nn.Conv2d(h.dense_channel, out_channel, (1, 2))
|
| 105 |
+
self.phase_conv_i = nn.Conv2d(h.dense_channel, out_channel, (1, 2))
|
| 106 |
+
|
| 107 |
+
def forward(self, x):
|
| 108 |
+
x = self.dense_block(x)
|
| 109 |
+
x = self.phase_conv(x)
|
| 110 |
+
x_r = self.phase_conv_r(x)
|
| 111 |
+
x_i = self.phase_conv_i(x)
|
| 112 |
+
x = torch.atan2(x_i, x_r)
|
| 113 |
+
x = x.permute(0, 3, 2, 1).squeeze(-1) # [B, F, T]
|
| 114 |
+
return x
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class TSTransformerBlock(nn.Module):
|
| 118 |
+
def __init__(self, h):
|
| 119 |
+
super(TSTransformerBlock, self).__init__()
|
| 120 |
+
self.h = h
|
| 121 |
+
self.time_transformer = TransformerBlock(d_model=h.dense_channel, n_heads=4)
|
| 122 |
+
self.freq_transformer = TransformerBlock(d_model=h.dense_channel, n_heads=4)
|
| 123 |
+
|
| 124 |
+
def forward(self, x):
|
| 125 |
+
b, c, t, f = x.size()
|
| 126 |
+
x = x.permute(0, 3, 2, 1).contiguous().view(b*f, t, c)
|
| 127 |
+
x = self.time_transformer(x) + x
|
| 128 |
+
x = x.view(b, f, t, c).permute(0, 2, 1, 3).contiguous().view(b*t, f, c)
|
| 129 |
+
x = self.freq_transformer(x) + x
|
| 130 |
+
x = x.view(b, t, f, c).permute(0, 3, 1, 2)
|
| 131 |
+
return x
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class MPNet(nn.Module):
|
| 135 |
+
def __init__(self, h, num_tsblocks=4):
|
| 136 |
+
super(MPNet, self).__init__()
|
| 137 |
+
self.h = h
|
| 138 |
+
self.num_tscblocks = num_tsblocks
|
| 139 |
+
self.dense_encoder = DenseEncoder(h, in_channel=2)
|
| 140 |
+
|
| 141 |
+
self.TSTransformer = nn.ModuleList([])
|
| 142 |
+
for i in range(num_tsblocks):
|
| 143 |
+
self.TSTransformer.append(TSTransformerBlock(h))
|
| 144 |
+
|
| 145 |
+
self.mask_decoder = MaskDecoder(h, out_channel=1)
|
| 146 |
+
self.phase_decoder = PhaseDecoder(h, out_channel=1)
|
| 147 |
+
|
| 148 |
+
def forward(self, noisy_amp, noisy_pha): # [B, F, T]
|
| 149 |
+
|
| 150 |
+
x = torch.stack((noisy_amp, noisy_pha), dim=-1).permute(0, 3, 2, 1) # [B, 2, T, F]
|
| 151 |
+
x = self.dense_encoder(x)
|
| 152 |
+
|
| 153 |
+
for i in range(self.num_tscblocks):
|
| 154 |
+
x = self.TSTransformer[i](x)
|
| 155 |
+
|
| 156 |
+
denoised_amp = noisy_amp * self.mask_decoder(x)
|
| 157 |
+
denoised_pha = self.phase_decoder(x)
|
| 158 |
+
denoised_com = torch.stack((denoised_amp*torch.cos(denoised_pha),
|
| 159 |
+
denoised_amp*torch.sin(denoised_pha)), dim=-1)
|
| 160 |
+
|
| 161 |
+
return denoised_amp, denoised_pha, denoised_com
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def phase_losses(phase_r, phase_g):
|
| 165 |
+
|
| 166 |
+
ip_loss = torch.mean(anti_wrapping_function(phase_r - phase_g))
|
| 167 |
+
gd_loss = torch.mean(anti_wrapping_function(torch.diff(phase_r, dim=1) - torch.diff(phase_g, dim=1)))
|
| 168 |
+
iaf_loss = torch.mean(anti_wrapping_function(torch.diff(phase_r, dim=2) - torch.diff(phase_g, dim=2)))
|
| 169 |
+
|
| 170 |
+
return ip_loss, gd_loss, iaf_loss
|
| 171 |
+
|
| 172 |
+
def anti_wrapping_function(x):
|
| 173 |
+
|
| 174 |
+
return torch.abs(x - torch.round(x / (2 * np.pi)) * 2 * np.pi)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def pesq_score(utts_r, utts_g, h):
|
| 178 |
+
|
| 179 |
+
pesq_score = Parallel(n_jobs=30)(delayed(eval_pesq)(
|
| 180 |
+
utts_r[i].squeeze().cpu().numpy(),
|
| 181 |
+
utts_g[i].squeeze().cpu().numpy(),
|
| 182 |
+
h.sampling_rate)
|
| 183 |
+
for i in range(len(utts_r)))
|
| 184 |
+
pesq_score = np.mean(pesq_score)
|
| 185 |
+
|
| 186 |
+
return pesq_score
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def eval_pesq(clean_utt, esti_utt, sr):
|
| 190 |
+
try:
|
| 191 |
+
pesq_score = pesq(sr, clean_utt, esti_utt)
|
| 192 |
+
except:
|
| 193 |
+
pesq_score = -1
|
| 194 |
+
|
| 195 |
+
return pesq_score
|
MP-SENet/models/transformer.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torch.nn import MultiheadAttention, GRU, Linear, LayerNorm, Dropout
|
| 5 |
+
|
| 6 |
+
class FFN(nn.Module):
|
| 7 |
+
def __init__(self, d_model, bidirectional=True, dropout=0):
|
| 8 |
+
super(FFN, self).__init__()
|
| 9 |
+
self.gru = GRU(d_model, d_model*2, 1, bidirectional=bidirectional)
|
| 10 |
+
if bidirectional:
|
| 11 |
+
self.linear = Linear(d_model*2*2, d_model)
|
| 12 |
+
else:
|
| 13 |
+
self.linear = Linear(d_model*2, d_model)
|
| 14 |
+
self.dropout = Dropout(dropout)
|
| 15 |
+
|
| 16 |
+
def forward(self, x):
|
| 17 |
+
self.gru.flatten_parameters()
|
| 18 |
+
x, _ = self.gru(x)
|
| 19 |
+
x = F.leaky_relu(x)
|
| 20 |
+
x = self.dropout(x)
|
| 21 |
+
x = self.linear(x)
|
| 22 |
+
|
| 23 |
+
return x
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TransformerBlock(nn.Module):
|
| 27 |
+
def __init__(self, d_model, n_heads, bidirectional=True, dropout=0):
|
| 28 |
+
super(TransformerBlock, self).__init__()
|
| 29 |
+
|
| 30 |
+
self.norm1 = LayerNorm(d_model)
|
| 31 |
+
self.attention = MultiheadAttention(d_model, n_heads, dropout=dropout)
|
| 32 |
+
self.dropout1 = Dropout(dropout)
|
| 33 |
+
|
| 34 |
+
self.norm2 = LayerNorm(d_model)
|
| 35 |
+
self.ffn = FFN(d_model, bidirectional=bidirectional)
|
| 36 |
+
self.dropout2 = Dropout(dropout)
|
| 37 |
+
|
| 38 |
+
self.norm3 = LayerNorm(d_model)
|
| 39 |
+
|
| 40 |
+
def forward(self, x, attn_mask=None, key_padding_mask=None):
|
| 41 |
+
xt = self.norm1(x)
|
| 42 |
+
xt, _ = self.attention(xt, xt, xt,
|
| 43 |
+
attn_mask=attn_mask,
|
| 44 |
+
key_padding_mask=key_padding_mask)
|
| 45 |
+
x = x + self.dropout1(xt)
|
| 46 |
+
|
| 47 |
+
xt = self.norm2(x)
|
| 48 |
+
xt = self.ffn(xt)
|
| 49 |
+
x = x + self.dropout2(xt)
|
| 50 |
+
|
| 51 |
+
x = self.norm3(x)
|
| 52 |
+
|
| 53 |
+
return x
|
| 54 |
+
|
| 55 |
+
def main():
|
| 56 |
+
x = torch.randn(4, 64, 401, 201)
|
| 57 |
+
b, c, t, f = x.size()
|
| 58 |
+
x = x.permute(0, 3, 2, 1).contiguous().view(b, f*t, c)
|
| 59 |
+
transformer = TransformerBlock(d_model=64, n_heads=4)
|
| 60 |
+
x = transformer(x)
|
| 61 |
+
x = x.view(b, f, t, c).permute(0, 3, 2, 1)
|
| 62 |
+
print(x.size())
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
if __name__ == '__main__':
|
| 66 |
+
main()
|
MP-SENet/utils.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import glob
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import matplotlib
|
| 6 |
+
matplotlib.use("Agg")
|
| 7 |
+
import matplotlib.pylab as plt
|
| 8 |
+
|
| 9 |
+
def plot_spectrogram(spectrogram):
|
| 10 |
+
fig, ax = plt.subplots(figsize=(4, 3))
|
| 11 |
+
im = ax.imshow(spectrogram, aspect="auto", origin="lower",
|
| 12 |
+
interpolation='none')
|
| 13 |
+
plt.colorbar(im, ax=ax)
|
| 14 |
+
fig.canvas.draw()
|
| 15 |
+
plt.close()
|
| 16 |
+
|
| 17 |
+
return fig
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def get_padding(kernel_size, dilation=1):
|
| 21 |
+
return int((kernel_size*dilation - dilation)/2)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_padding_2d(kernel_size, dilation=(1, 1)):
|
| 25 |
+
return (int((kernel_size[0]*dilation[0] - dilation[0])/2), int((kernel_size[1]*dilation[1] - dilation[1])/2))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class LearnableSigmoid1d(nn.Module):
|
| 29 |
+
def __init__(self, in_features, beta=1):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.beta = beta
|
| 32 |
+
self.slope = nn.Parameter(torch.ones(in_features))
|
| 33 |
+
self.slope.requiresGrad = True
|
| 34 |
+
|
| 35 |
+
def forward(self, x):
|
| 36 |
+
return self.beta * torch.sigmoid(self.slope * x)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class LearnableSigmoid2d(nn.Module):
|
| 40 |
+
def __init__(self, in_features, beta=1):
|
| 41 |
+
super().__init__()
|
| 42 |
+
self.beta = beta
|
| 43 |
+
self.slope = nn.Parameter(torch.ones(in_features, 1))
|
| 44 |
+
self.slope.requiresGrad = True
|
| 45 |
+
|
| 46 |
+
def forward(self, x):
|
| 47 |
+
return self.beta * torch.sigmoid(self.slope * x)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class Sigmoid2d(nn.Module):
|
| 51 |
+
def __init__(self, in_features, beta=1):
|
| 52 |
+
super().__init__()
|
| 53 |
+
self.beta = beta
|
| 54 |
+
self.slope = torch.ones(in_features, 1)
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
return self.beta * torch.sigmoid(self.slope * x)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class PLSigmoid(nn.Module):
|
| 61 |
+
def __init__(self, in_features):
|
| 62 |
+
super().__init__()
|
| 63 |
+
self.beta = nn.Parameter(torch.ones(in_features, 1) * 2.0)
|
| 64 |
+
self.slope = nn.Parameter(torch.ones(in_features, 1))
|
| 65 |
+
self.beta.requiresGrad = True
|
| 66 |
+
self.slope.requiresGrad = True
|
| 67 |
+
|
| 68 |
+
def forward(self, x):
|
| 69 |
+
return self.beta * torch.sigmoid(self.slope * x)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def load_checkpoint(filepath, device):
|
| 73 |
+
assert os.path.isfile(filepath)
|
| 74 |
+
print("Loading '{}'".format(filepath))
|
| 75 |
+
checkpoint_dict = torch.load(filepath, map_location=device)
|
| 76 |
+
print("Complete.")
|
| 77 |
+
return checkpoint_dict
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def save_checkpoint(filepath, obj):
|
| 81 |
+
print("Saving checkpoint to {}".format(filepath))
|
| 82 |
+
torch.save(obj, filepath)
|
| 83 |
+
print("Complete.")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def scan_checkpoint(cp_dir, prefix):
|
| 87 |
+
pattern = os.path.join(cp_dir, prefix + '????????')
|
| 88 |
+
cp_list = glob.glob(pattern)
|
| 89 |
+
if len(cp_list) == 0:
|
| 90 |
+
return None
|
| 91 |
+
return sorted(cp_list)[-1]
|
| 92 |
+
|
| 93 |
+
def main():
|
| 94 |
+
plsigmoid = PLSigmoid(201)
|
| 95 |
+
a = torch.randn(4, 201, 100)
|
| 96 |
+
print(plsigmoid(a))
|
| 97 |
+
|
| 98 |
+
if __name__ == '__main__':
|
| 99 |
+
main()
|