Macでtensorflow2でtacotron2の音声合成できるまで
自分の声で音声合成やりたい。
概要
Mac M2 + tensorflow2 + tacotron2で学習/推論できる環境構築。
大変だった。
やっと落ち着いたのでメモを残しておく。
ベースとなるソースは NVIDIA/tacotron2 (opens new window)にある。
環境
- Mac OS Ventura 13.4.1 (c)
- Chip Apple M2
- Python 3.10.12
ファイル修正
クローンしてきたコードを修正していく。
requirements.txt
falcon==1.2.0
inflect==0.2.5
librosa==0.9.1
Unidecode==0.4.20
torch==2.0.1
torchaudio==2.0.2
torchvision==0.15.2
tensorflow==2.13.0
tensorflow-estimator==2.13.0
typing_extensions==4.5.0
IPython
layers.py
librosa_mel_fn関数の引数をキーワード指定に修正。 (Mac直だと問題ないけど、Dockerで動かした時に引っかかった気がする)
class TacotronSTFT(torch.nn.Module):
def __init__(self, filter_length=1024, hop_length=256, win_length=1024,
...
mel_basis = librosa_mel_fn(
# sampling_rate, filter_length, n_mel_channels, mel_fmin, mel_fmax)
sr=sampling_rate, n_fft=filter_length, n_mels=n_mel_channels, fmin=mel_fmin, fmax=mel_fmax)
stft.py
キーワード指定に修正
エラー: stft.py:67: FutureWarning: Pass size=1024 as keyword args. From version 0.10 passing these as positional arguments will result in an error
# fft_window = pad_center(fft_window, filter_length)
fft_window = pad_center(fft_window, size=filter_length)
train.py
cuda()
をto("cpu")
とかto("mps")
に変換。
結構cuda関連でハマった。軒並み変換した。
def load_model(hparams):
# model = Tacotron2(hparams).cuda()
model = Tacotron2(hparams).to("cpu")
utils.py
cuda
をmps
に変換している
def get_mask_from_lengths(lengths):
max_len = torch.max(lengths).item()
# ids = torch.arange(0, max_len, out=torch.cuda.LongTensor(max_len))
ids = torch.arange(0, max_len, out=torch.mps.Tensor(max_len))
こっちの関数もcuda
をmps
に変換。
def to_gpu(x):
x = x.contiguous()
if torch.cuda.is_available():
# x = x.cuda(non_blocking=True)
x = x.to("mps" ,non_blocking=True)
waveglow/denoiser.py
ここはたしか推論の時に使うのだが、mps
にするとcpu
と混合してると怒られたのでcpu
に統一した。
エラー: RuntimeError: slow_conv2d_forward_mps: input(device='cpu') and weight(device=mps:0') must be on the same device
class Denoiser(torch.nn.Module):
""" Removes model bias from audio produced with waveglow """
def __init__(self, waveglow, filter_length=1024, n_overlap=4,
win_length=1024, mode='zeros'):
super(Denoiser, self).__init__()
# self.stft = STFT(filter_length=filter_length,
# hop_length=int(filter_length/n_overlap),
# win_length=win_length).cuda()
self.stft = STFT(filter_length=filter_length,
hop_length=int(filter_length/n_overlap),
win_length=win_length).to("cpu")
waveglow/glow.py
ここもcuda
from torch import mps
...
def infer(self, spect, sigma=1.0):
...
# audio = torch.cuda.FloatTensor(spect.size(0),
audio = mps.Tensor(spect.size(0),
self.n_remaining_channels,
spect.size(2)).normal_()
ここも。
def infer(self, spect, sigma=1.0):
...
# z = torch.cuda.FloatTensor(spect.size(0), self.n_early_size, spect.size(2)).normal_()
# z = mps.Tensor(spect.size(0), self.n_early_size, spect.size(2)).normal_()
z = torch.FloatTensor(spect.size(0), self.n_early_size, spect.size(2)).normal_()
inference.py
推論用のスクリプトを作成。ファイル名は適当。
最後に音声ファイルを出力してる。
import matplotlib
import matplotlib.pylab as plt
import IPython.display as ipd
import sys
sys.path.append('waveglow/')
import numpy as np
import torchaudio
import torch
from torch import mps
from hparams import create_hparams
from model import Tacotron2
from layers import TacotronSTFT, STFT
from audio_processing import griffin_lim
from train import load_model
from text import text_to_sequence
from denoiser import Denoiser
def plot_data(data, figsize=(16, 4)):
fig, axes = plt.subplots(1, len(data), figsize=figsize)
for i in range(len(data)):
# axes[i].imshow(data[i], aspect='auto', origin='bottom',
axes[i].imshow(data[i], aspect='auto', origin='lower',
interpolation='none')
hparams = create_hparams()
checkpoint_path = "outdir/checkpoint_1000"
model = load_model(hparams)
model.load_state_dict(torch.load(checkpoint_path)['state_dict'])
# _ = model.cuda().eval().half()
_ = model.to("cpu").eval()
# _ = model.to("mps").eval().half()
waveglow_path = 'waveglow_256channels_universal_v5.pt'
waveglow = torch.load(waveglow_path)['model']
# waveglow.cuda().eval().half()
waveglow.to("cpu").eval()
# waveglow.to("mps").eval().half()
for k in waveglow.convinv:
k.float()
denoiser = Denoiser(waveglow)
text = "konnitiwa"
sequence = np.array(text_to_sequence(text, ['basic_cleaners']))[None, :]
sequence = torch.autograd.Variable(
# torch.from_numpy(sequence)).cuda().long()
torch.from_numpy(sequence)).to("cpu").long()
mel_outputs, mel_outputs_postnet, _, alignments = model.inference(sequence)
plot_data((mel_outputs.float().data.cpu().numpy()[0],
mel_outputs_postnet.float().data.cpu().numpy()[0],
alignments.float().data.cpu().numpy()[0].T))
with torch.no_grad():
audio = waveglow.infer(mel_outputs_postnet, sigma=0.666)
# ipd.Audio(audio[0].data.cpu().numpy(), rate=hparams.sampling_rate)
audio_tensor = torch.from_numpy(audio[0].data.cpu().numpy())
audio_tensor = audio_tensor.unsqueeze(0) # 2次元のテンソルに変換
torchaudio.save(filepath='result.wav', src=audio_tensor, sample_rate=hparams.sampling_rate)
感想
Epochを少なくトレーニングすると「ザー」っていうノイズしか生成できなかったが、値を増やしていくとちゃんと音声になった。
トレーニングした言葉はうまく話せるけど、それ以外の言葉だと変なこと言い出すからその辺改善していきたい。
参考サイト
他の情報は以下を参考にした。
- https://trend-tracer.com/python-tacotron2/ (opens new window)
- https://note.com/npaka/n/n2a91c3ca9f34 (opens new window)
以上。