화자의 목소리를 담은 노래 커버를 제작하는 ai 서비스를 개발중에 있다. 이를 위해 RVC(Retrieval-based Voice Conversion)라는 AI 음성 합성 기술을 사용한다. RVC에서 많이 사용하는 깃헙 레포지토리는 https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI/tree/main 이다. 하지만 레포지토리 이름에서 보이는 대로 이는 그라디오로 만들어진 웹 상에서 작동하도록 설계된 레포지토리이다. 서비스 제작을 위해서는 코드를 분석하여 api로 만들 수 있도록 코드를 수정해야한다. 이 포스트에서는 서비스를 위한 AI 기능 구현을 위한 코드 분석 과정을 담는다.
서비스 상에서 구현되어야하는 AI 기능은 아래와 같다.
- 노래에서 가수 목소리 음원과 MR 음원으로 분리
- 화자의 목소리를 받아서 화자 목소리 모델을 생성
- 화자 목소리 모델과 가수 목소리 음원을 합성하여 화자 목소리로 부른 음원을 생성
RVC 깃헙 코드상에서 이 기능들이 모두 구현되어 있다. 포스트 당 하나씩 살펴보자. 이번 포스팅에는 “화자의 목소리를 받아서 화자 목소리 모델을 생성”에 대해 알아본다.
화자의 목소리를 받아서 화자 목소리 모델을 생성
화자 목소리 모델을 훈련하는 것은 웹 상에서는 24번 “원클릭 훈련” 버튼을 클릭하면 데이터 전처리(8), 특징추출(12), 모델 훈련(22), 특징 인덱스 훈련(24) 이 4가지 과정을 거쳐서 화자 목소리 모델이 생성된다.
각 빨간색 네모 영역은 다음을 의미한다. 카테고리는 특정 네모 영역이 어떤 과정을 위한 것인지 표기하기 위해 나타낸다. 풀어 설명하면 스크린샷 번호가 카테고리 번호가 2으로 되어있다면 특징 추출에 그 스크린샷 번호 영역이 사용된다는 것이다. (이런 카테고리 번호가 붙어진 이유는 아래에 나온다.)
- 1: 데이터 전처리
- 2: 특징 추출
- 3: 모델 훈련
- 4: 특징 인덱스 훈련
스크린샷 번호 | 의미 | 카테고리 |
---|---|---|
1 | 화자 목소리 프로젝트 이름 입력 | 1, 2, 3, 4 |
2 | 샘플링 ratio | 1, 3 |
3 | 화자 목소리 파일이 음높이를 포함하는지.(화자 목소리가 노래로 부터 추출되었으면 “예”를 아니면 “아니오”를 선택) | 2, 3 |
4 | 모델 버전 | 2, 3, 4 |
5 | cpu 프로세스 수 | 1, 2 |
6 | 음성 파일이 담긴 폴더 경로 | 1 |
7 | 화자 ID | 3 |
8 | 데이터 처리 실행 버튼 | 1 |
9 | gpu 카드 번호 | - |
10 | 특징 추출 알고리즘 | 2 |
11 | 알고리즘 rmvpe 사용할 때의 프로세스 번호 | 2 |
12 | 특징 추출 실행 버튼 | 2 |
13 | 모델 저장 빈도 | 3 |
14 | 총 훈련 epoch | 3 |
15 | 배치사이즈 | 3 |
16 | 디스크 공간을 절약하기 위해 최신 모델 파일만 저장할지 | 3 |
17 | 모든 훈련 세트를 VRAM에 캐시할지 여부. 10분 미만의 소량 데이터는 캐시하여 훈련 속도를 높일 수 있지만, 대량 데이터 캐시는 VRAM을 과부하시키고 속도를 크게 향상시키지 못함 | 3 |
18 | 저장시마다 모델을 weights 폴더에 저장할지 | 3 |
19 | 미리 훈련된 G 모델 경로 | 3 |
20 | 미리 훈련된 G 모델 경로 | 3 |
21 | gpu 카드 번호 | 2, 3 |
22 | 모델 훈련 실행 버튼 | 3 |
23 | 특징 인덱스 훈련 실행 버튼 | 4 |
24 | 전체 훈련 실행 버튼 | - |
이제 이 스크린샷의 부분을 코드에서 찾아보자. 위와 같이 WEB을 표현하는 gradio가 담겨있는 파일은 infer-web.py이다. 24번(원클릭훈련) 버튼을 눌렀을 때의 코드는 아래와 같다. but5 버튼이 25번 영역인 전체 훈련 실행 버튼이다. 코드에 대해 간략하게 설명하면 but5를 클릭하면 train1key 함수에 exp_dir1, sr2등의 파라미터가 들어가서 실행된다. 그리고 그 결과물이 info3에 담기게 된다.
but5.click(
train1key,
[
exp_dir1,
sr2,
if_f0_3,
trainset_dir4,
spk_id5,
np7,
f0method8,
save_epoch10,
total_epoch11,
batch_size12,
if_save_latest13,
pretrained_G14,
pretrained_D15,
gpus16,
if_cache_gpu17,
if_save_every_weights18,
version19,
gpus_rmvpe,
],
info3,
api_name="train_start_all",
)
but5를 클릭하면 실행되는 train1key 함수를 살펴보자.
def train1key(
exp_dir1,
sr2,
if_f0_3,
trainset_dir4,
spk_id5,
np7,
f0method8,
save_epoch10,
total_epoch11,
batch_size12,
if_save_latest13,
pretrained_G14,
pretrained_D15,
gpus16,
if_cache_gpu17,
if_save_every_weights18,
version19,
gpus_rmvpe,
):
infos = []
def get_info_str(strr):
infos.append(strr)
return "\n".join(infos)
# step1:处理数据
yield get_info_str(i18n("step1:正在处理数据"))
[get_info_str(_) for _ in preprocess_dataset(trainset_dir4, exp_dir1, sr2, np7)]
# step2a:提取音高
yield get_info_str(i18n("step2:正在提取音高&正在提取特征"))
[
get_info_str(_)
for _ in extract_f0_feature(
gpus16, np7, f0method8, if_f0_3, exp_dir1, version19, gpus_rmvpe
)
]
# step3a:训练模型
yield get_info_str(i18n("step3a:正在训练模型"))
click_train(
exp_dir1,
sr2,
if_f0_3,
spk_id5,
save_epoch10,
total_epoch11,
batch_size12,
if_save_latest13,
pretrained_G14,
pretrained_D15,
gpus16,
if_cache_gpu17,
if_save_every_weights18,
version19,
)
yield get_info_str(
i18n("训练结束, 您可查看控制台训练日志或实验文件夹下的train.log")
)
# step3b:训练索引
[get_info_str(_) for _ in train_index(exp_dir1, version19)]
yield get_info_str(i18n("全流程结束!"))
주석을 확인하면 스크린샷에서 본 것 처럼 4 steps로 나눠져 있는 것을 확인할 수 있다.
step 번호 | 의미 | 함수 | 입력되는 파라미터 |
---|---|---|---|
step 1 | 데이터 전처리 | preprocess_dataset | trainset_dir4, exp_dir1, sr2, np7 |
step 2 | 특징 추출 | extract_f0_feature | gpus16, np7, f0method8, if_f0_3, exp_dir1, version19, gpus_rmvpe |
step 3-a | 모델 훈련 | click_train | exp_dir1, sr2, if_f0_3, spk_id5, save_epoch10, total_epoch11, batch_size12, if_save_latest13, pretrained_G14, pretrained_D15, gpus16, if_cache_gpu17, if_save_every_weights18, version19 |
step 3-b | 특징 인덱스 훈련 | train_index | exp_dir1, version19 |
이제 파라미터 변수명과 웹 스크린샷의 번호를 코드를 보고 연결해보면 아래와 같다.
스크린샷 번호 | 의미 | 카테고리 | 파라미터 변수명 |
---|---|---|---|
1 | 화자 목소리 프로젝트 이름 입력 | 1, 2, 3, 4 | exp_dir1 |
2 | 샘플링 ratio | 1, 3 | sr2 |
3 | 화자 목소리 파일이 음높이를 포함하는지.(화자 목소리가 노래로 부터 추출되었으면 “예”를 아니면 “아니오”를 선택) | 2, 3 | if_f0_3 |
4 | 모델 버전 | 2, 3, 4 | version19 |
5 | cpu 프로세스 수 | 1, 2 | np7 |
6 | 음성 파일이 담긴 폴더 경로 | 1 | trainset_dir4 |
7 | 화자 ID | 3 | spk_id5 |
8 | 데이터 처리 실행 버튼 | 1 | but1 |
9 | gpu 카드 번호 | - | gpus6 |
10 | 특징 추출 알고리즘 | 2 | f0method8 |
11 | 알고리즘 rmvpe 사용할 때의 프로세스 번호 | 2 | gpus_rmvpe |
12 | 특징 추출 실행 버튼 | 2 | but2 |
13 | 모델 저장 빈도 | 3 | save_epoch10 |
14 | 총 훈련 epoch | 3 | total_epoch11 |
15 | 배치사이즈 | 3 | batch_size12 |
16 | 디스크 공간을 절약하기 위해 최신 모델 파일만 저장할지 | 3 | if_save_latest13 |
17 | 모든 훈련 세트를 VRAM에 캐시할지 여부. 10분 미만의 소량 데이터는 캐시하여 훈련 속도를 높일 수 있지만, 대량 데이터 캐시는 VRAM을 과부하시키고 속도를 크게 향상시키지 못함 | 3 | if_cache_gpu17 |
18 | 저장시마다 모델을 weights 폴더에 저장할지 | 3 | if_save_every_weights18 |
19 | 미리 훈련된 G 모델 경로 | 3 | pretrained_G14 |
20 | 미리 훈련된 G 모델 경로 | 3 | pretrained_D15 |
21 | gpu 카드 번호 | 2, 3 | gpus16 |
22 | 모델 훈련 실행 버튼 | 3 | but3 |
23 | 특징 인덱스 훈련 실행 버튼 | 4 | but4 |
24 | 전체 훈련 실행 버튼 | - | but5 |
이제 각 step에 해당하는 함수를 분석해보자.
데이터 전처리(preprocess_dataset 함수)
def preprocess_dataset(trainset_dir, exp_dir, sr, n_p):
sr = sr_dict[sr]
os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True)
f = open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "w")
f.close()
cmd = '"%s" infer/modules/train/preprocess.py "%s" %s %s "%s/logs/%s" %s %.1f' % (
config.python_cmd,
trainset_dir,
sr,
n_p,
now_dir,
exp_dir,
config.noparallel,
config.preprocess_per,
)
logger.info("Execute: " + cmd)
# , stdin=PIPE, stdout=PIPE,stderr=PIPE,cwd=now_dir
p = Popen(cmd, shell=True)
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done,
args=(
done,
p,
),
).start()
while 1:
with open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "r") as f:
yield (f.read())
sleep(1)
if done[0]:
break
with open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "r") as f:
log = f.read()
logger.info(log)
yield log
코드의 주요한 부분은 “infer/modules/train/preprocess.py”파일을 실행시킨다는 점이다. 저 preprocess.py의 코드까지는 서비스 제작을 위해서는 이해가 필요없기 때문에 데이터 전처리 코드 분석은 여기까지 진행한다. 서비스 제작을 위해서 약간의 수정을 거쳐서 아래와 같은 새로운 파일을 만들어주었다.
# 새롭게 작성된 preprocess.py
import os
import sys
import argparse
import logging
from time import sleep
from subprocess import Popen
import threading
# Directory setup
now_dir = os.getcwd()
sys.path.append(now_dir)
from configs.config import Config
# Set logging levels
logging.getLogger("numba").setLevel(logging.WARNING)
logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
# Sampling rate dictionary
sr_dict = {
"32k": 32000,
"40k": 40000,
"48k": 48000,
}
# Configuration from external file
config = Config()
def if_done(done, p):
p.wait()
done[0] = True
def preprocess_dataset(trainset_dir, exp_dir, sr, n_p):
os.makedirs(trainset_dir, exist_ok=True)
sr = sr_dict[sr]
log_dir = f"{now_dir}/logs/{exp_dir}"
os.makedirs(log_dir, exist_ok=True)
cmd = f'"{config.python_cmd}" infer/modules/train/preprocess.py "{trainset_dir}" {sr} {n_p} "{log_dir}" {config.noparallel} {config.preprocess_per}'
logger.info("Execute: " + cmd)
p = Popen(cmd, shell=True)
done = [False]
threading.Thread(target=if_done, args=(done, p)).start()
def arg_parse():
parser = argparse.ArgumentParser(description="Preprocess audio data for training.")
parser.add_argument('--trainset_dir', type=str, default="/home/choi/desktop/rvc/ai/data/user1/input/speaker",
help="Directory where the training dataset is stored")
parser.add_argument('--exp_dir', type=str, default="../../data/user1/output/trained_model",
help="Directory where the experiment's outputs are saved")
parser.add_argument('--sampling_rate', type=str, choices=['32k', '40k', '48k'], default="40k",
help="Sampling rate for the audio processing")
parser.add_argument('--n_p', type=int, default=8,
help="Number of processes to use")
return parser.parse_args()
def main():
args = arg_parse()
preprocess_dataset(args.trainset_dir, args.exp_dir, args.sampling_rate, args.n_p)
if __name__ == "__main__":
main()
특징 추출(extract_f0_feature 함수)
def extract_f0_feature(gpus, n_p, f0method, if_f0, exp_dir, version19, gpus_rmvpe):
gpus = gpus.split("-")
os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True)
f = open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "w")
f.close()
if if_f0:
if f0method != "rmvpe_gpu":
cmd = (
'"%s" infer/modules/train/extract/extract_f0_print.py "%s/logs/%s" %s %s'
% (
config.python_cmd,
now_dir,
exp_dir,
n_p,
f0method,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , stdin=PIPE, stdout=PIPE,stderr=PIPE
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done,
args=(
done,
p,
),
).start()
else:
if gpus_rmvpe != "-":
gpus_rmvpe = gpus_rmvpe.split("-")
leng = len(gpus_rmvpe)
ps = []
for idx, n_g in enumerate(gpus_rmvpe):
cmd = (
'"%s" infer/modules/train/extract/extract_f0_rmvpe.py %s %s %s "%s/logs/%s" %s '
% (
config.python_cmd,
leng,
idx,
n_g,
now_dir,
exp_dir,
config.is_half,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
ps.append(p)
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done_multi, #
args=(
done,
ps,
),
).start()
else:
cmd = (
config.python_cmd
+ ' infer/modules/train/extract/extract_f0_rmvpe_dml.py "%s/logs/%s" '
% (
now_dir,
exp_dir,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
p.wait()
done = [True]
while 1:
with open(
"%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r"
) as f:
yield (f.read())
sleep(1)
if done[0]:
break
with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
log = f.read()
logger.info(log)
yield log
# 对不同part分别开多进程
"""
n_part=int(sys.argv[1])
i_part=int(sys.argv[2])
i_gpu=sys.argv[3]
exp_dir=sys.argv[4]
os.environ["CUDA_VISIBLE_DEVICES"]=str(i_gpu)
"""
leng = len(gpus)
ps = []
for idx, n_g in enumerate(gpus):
cmd = (
'"%s" infer/modules/train/extract_feature_print.py %s %s %s %s "%s/logs/%s" %s %s'
% (
config.python_cmd,
config.device,
leng,
idx,
n_g,
now_dir,
exp_dir,
version19,
config.is_half,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
ps.append(p)
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done_multi,
args=(
done,
ps,
),
).start()
while 1:
with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
yield (f.read())
sleep(1)
if done[0]:
break
with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
log = f.read()
logger.info(log)
yield log
나는 특징 추출 알고리즘으로 rmvpe-gpu를 사용할 것이다. 그 관점에서 이 코드에서도 주요부분은 infer/modules/train/extract/extract_f0_rmvpe.py 파일을 실행한다는 것이다. 이번에도 서비스 제작을 위해서 코드 이해는 이 정도면 충분하기 때문에 더 깊게 들어가지 않는다. 그리고 위 코드의 내용을 필요한 부분만 정리해서 아래와 같이 새로운 파일을 만들었다.
import os
import sys
now_dir = os.getcwd()
sys.path.append(now_dir)
import argparse
import logging
from time import sleep
from subprocess import Popen
import threading
from configs.config import Config
logging.getLogger("numba").setLevel(logging.WARNING)
logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
config = Config()
def if_done(done, p):
while 1:
if p.poll() is None:
sleep(0.5)
else:
break
done[0] = True
def if_done_multi(done, ps):
while 1:
# poll==None代表进程未结束
# 只要有一个进程未结束都不停
flag = 1
for p in ps:
if p.poll() is None:
flag = 0
sleep(0.5)
break
if flag == 1:
break
done[0] = True
def extract_f0_feature(gpus, n_p, f0method, if_f0, exp_dir, version19, gpus_rmvpe):
gpus = gpus.split("-")
os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True)
f = open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "w")
f.close()
if if_f0:
if f0method != "rmvpe_gpu":
cmd = (
'"%s" infer/modules/train/extract/extract_f0_print.py "%s/logs/%s" %s %s'
% (
config.python_cmd,
now_dir,
exp_dir,
n_p,
f0method,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , stdin=PIPE, stdout=PIPE,stderr=PIPE
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done,
args=(
done,
p,
),
).start()
else:
if gpus_rmvpe != "-":
gpus_rmvpe = gpus_rmvpe.split("-")
leng = len(gpus_rmvpe)
ps = []
for idx, n_g in enumerate(gpus_rmvpe):
cmd = (
'"%s" infer/modules/train/extract/extract_f0_rmvpe.py %s %s %s "%s/logs/%s" %s '
% (
config.python_cmd,
leng,
idx,
n_g,
now_dir,
exp_dir,
config.is_half,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
ps.append(p)
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done_multi, #
args=(
done,
ps,
),
).start()
else:
cmd = (
config.python_cmd
+ ' infer/modules/train/extract/extract_f0_rmvpe_dml.py "%s/logs/%s" '
% (
now_dir,
exp_dir,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
p.wait()
done = [True]
while 1:
with open(
"%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r"
) as f:
yield (f.read())
sleep(1)
if done[0]:
break
with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
log = f.read()
logger.info(log)
yield log
# 对不同part分别开多进程
"""
n_part=int(sys.argv[1])
i_part=int(sys.argv[2])
i_gpu=sys.argv[3]
exp_dir=sys.argv[4]
os.environ["CUDA_VISIBLE_DEVICES"]=str(i_gpu)
"""
leng = len(gpus)
ps = []
for idx, n_g in enumerate(gpus):
cmd = (
'"%s" infer/modules/train/extract_feature_print.py %s %s %s %s "%s/logs/%s" %s %s'
% (
config.python_cmd,
config.device,
leng,
idx,
n_g,
now_dir,
exp_dir,
version19,
config.is_half,
)
)
logger.info("Execute: " + cmd)
p = Popen(
cmd, shell=True, cwd=now_dir
) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir
ps.append(p)
# 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读
done = [False]
threading.Thread(
target=if_done_multi,
args=(
done,
ps,
),
).start()
while 1:
with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
yield (f.read())
sleep(1)
if done[0]:
break
with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f:
log = f.read()
logger.info(log)
yield log
def arg_parse():
parser = argparse.ArgumentParser(description="Process GPU configurations and settings for feature extraction.")
parser.add_argument('--gpus', type=str, default="0", help='GPU devices to use, separated by "-"')
parser.add_argument('--n_p', type=int, default=8, help='Number of processes to use')
parser.add_argument('--f0method', type=str, default="rmvpe_gpu", help='Method for F0 extraction')
parser.add_argument('--if_f0', type=bool, default=True, help='Boolean to determine if F0 extraction is needed')
parser.add_argument('--exp_dir', type=str, default="../../data/user1/output/trained_model", help='Directory for experiment outputs')
parser.add_argument('--version19', type=str, default="v2", help='Version of the model to use')
parser.add_argument('--gpus_rmvpe', type=str, default="0-0", help='Specific GPUs for rmvpe method')
return parser.parse_args()
def main():
args = arg_parse()
for log in extract_f0_feature(args.gpus, args.n_p, args.f0method, args.if_f0, args.exp_dir, args.version19, args.gpus_rmvpe):
print(log)
if __name__ == "__main__":
main()
이와 같은 방식으로 “모델 훈련” 과정과 “특징 인덱스 훈련” 과정도 분석을 하고 새로운 파일을 만들었다. 여기서는 코드가 너무 긴 관계로 적지 않겠다.
'서비스 제작' 카테고리의 다른 글
[UVR 음원 분리] 음원 분리 잡음 해결 (0) | 2024.05.19 |
---|---|
[RVC 코드 분석] 화자 목소리 모델과 가수 목소리 음원을 합성하여 화자 목소리로 부른 음원을 생성 (0) | 2024.04.17 |
[RVC 코드 분석] 노래에서 가수 목소리 음원과 MR 음원으로 분리 (0) | 2024.04.17 |
[MDQA]1.3 자른 텍스트를 특정 크기의 벡터로 임베딩(embedder 모듈) (0) | 2024.03.20 |
[MDQA]1.2 텍스트 데이터를 chunk로 자르기(chunker 모듈) (0) | 2024.03.17 |