mirror of
https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git
synced 2026-08-29 10:09:32 +02:00
175 lines
5.5 KiB
Python
175 lines
5.5 KiB
Python
import os
|
||
import platform
|
||
import sys
|
||
import traceback
|
||
import glob
|
||
|
||
import faiss
|
||
import numpy as np
|
||
from sklearn.cluster import MiniBatchKMeans
|
||
from i18n.i18n import I18nAuto
|
||
from tools.progress import should_report
|
||
|
||
|
||
i18n = I18nAuto()
|
||
|
||
|
||
exp_name = sys.argv[1]
|
||
version = sys.argv[2]
|
||
outside_index_root = sys.argv[3]
|
||
n_cpu = int(sys.argv[4])
|
||
exp_dir = os.path.join("logs", exp_name)
|
||
feature_dir = os.path.join(
|
||
exp_dir, "3_feature256" if version == "v1" else "3_feature768"
|
||
)
|
||
log_path = os.path.join(exp_dir, "train_index.log")
|
||
os.makedirs(exp_dir, exist_ok=True)
|
||
|
||
|
||
def log(message):
|
||
print(message, flush=True)
|
||
with open(log_path, "a", encoding="utf8") as f:
|
||
f.write(str(message) + "\n")
|
||
|
||
|
||
with open(log_path, "w", encoding="utf8"):
|
||
pass
|
||
|
||
|
||
def newest_index(pattern):
|
||
paths = [path for path in glob.glob(pattern) if os.path.isfile(path)]
|
||
return max(paths, key=os.path.getmtime) if paths else ""
|
||
|
||
|
||
def link_added_index(added_path):
|
||
added_name = os.path.basename(added_path)
|
||
try:
|
||
os.makedirs(outside_index_root, exist_ok=True)
|
||
source = os.path.abspath(added_path)
|
||
outside_root = os.path.abspath(outside_index_root)
|
||
if os.path.commonpath([source, outside_root]) == outside_root:
|
||
log(i18n("[索引训练] 外部索引链接已存在:%s") % source)
|
||
return
|
||
target = os.path.abspath(
|
||
os.path.join(outside_index_root, "%s_%s" % (exp_name, added_name))
|
||
)
|
||
if os.path.lexists(target):
|
||
try:
|
||
if os.path.samefile(source, target):
|
||
log(i18n("[索引训练] 外部索引链接已存在:%s") % target)
|
||
return
|
||
except (FileNotFoundError, OSError):
|
||
pass
|
||
os.unlink(target)
|
||
if platform.system() == "Windows":
|
||
os.link(source, target)
|
||
else:
|
||
os.symlink(source, target)
|
||
log(i18n("[索引训练] 已链接索引到外部目录:%s") % outside_index_root)
|
||
except Exception:
|
||
log(
|
||
i18n("[索引训练][失败] 无法链接索引到外部目录:%s\n%s")
|
||
% (outside_index_root, traceback.format_exc())
|
||
)
|
||
|
||
|
||
existing_trained_path = newest_index(
|
||
os.path.join(
|
||
exp_dir,
|
||
"trained_IVF*_Flat_nprobe_*_%s_%s.index" % (exp_name, version),
|
||
)
|
||
)
|
||
existing_added_path = newest_index(
|
||
os.path.join(exp_dir, "added_IVF*_Flat_nprobe_*_%s_%s.index" % (exp_name, version))
|
||
)
|
||
if not existing_added_path:
|
||
existing_added_path = newest_index(
|
||
os.path.join(
|
||
outside_index_root,
|
||
"%s_*added_IVF*_Flat_nprobe_*_%s_%s.index"
|
||
% (exp_name, exp_name, version),
|
||
)
|
||
)
|
||
if existing_added_path:
|
||
if existing_trained_path:
|
||
log(
|
||
i18n("[索引训练][跳过] trained索引已存在:%s")
|
||
% os.path.basename(existing_trained_path)
|
||
)
|
||
log(
|
||
i18n("[索引训练][跳过] added索引已存在:%s")
|
||
% os.path.basename(existing_added_path)
|
||
)
|
||
link_added_index(existing_added_path)
|
||
raise SystemExit(0)
|
||
|
||
if not os.path.isdir(feature_dir) or not os.listdir(feature_dir):
|
||
log(i18n("[索引训练][失败] 请先进行特征提取"))
|
||
raise SystemExit(1)
|
||
|
||
features = []
|
||
for name in sorted(os.listdir(feature_dir)):
|
||
features.append(np.load(os.path.join(feature_dir, name)))
|
||
|
||
big_npy = np.concatenate(features, 0)
|
||
big_npy = big_npy[np.random.permutation(big_npy.shape[0])]
|
||
if big_npy.shape[0] > 200000:
|
||
log(i18n("[索引训练] 正在将%s条特征聚类为10000个中心") % big_npy.shape[0])
|
||
try:
|
||
big_npy = MiniBatchKMeans(
|
||
n_clusters=10000,
|
||
verbose=False,
|
||
batch_size=256 * n_cpu,
|
||
compute_labels=False,
|
||
init="random",
|
||
).fit(big_npy).cluster_centers_
|
||
except Exception:
|
||
log(i18n("[索引训练][失败] 聚类失败,将使用原始特征继续\n%s") % traceback.format_exc())
|
||
|
||
n_ivf = max(1, min(int(16 * np.sqrt(big_npy.shape[0])), big_npy.shape[0] // 39))
|
||
log(i18n("[索引训练] 特征形状:%s | IVF数量:%s") % (big_npy.shape, n_ivf))
|
||
if existing_trained_path:
|
||
trained_path = existing_trained_path
|
||
index = faiss.read_index(trained_path)
|
||
index_ivf = faiss.extract_index_ivf(index)
|
||
index_ivf.nprobe = 1
|
||
log(
|
||
i18n("[索引训练][跳过] trained索引已存在:%s")
|
||
% os.path.basename(trained_path)
|
||
)
|
||
else:
|
||
index = faiss.index_factory(
|
||
256 if version == "v1" else 768, "IVF%s,Flat" % n_ivf
|
||
)
|
||
index_ivf = faiss.extract_index_ivf(index)
|
||
index_ivf.nprobe = 1
|
||
trained_path = os.path.join(
|
||
exp_dir,
|
||
"trained_IVF%s_Flat_nprobe_%s_%s_%s.index"
|
||
% (n_ivf, index_ivf.nprobe, exp_name, version),
|
||
)
|
||
log(i18n("[索引训练] 正在训练索引"))
|
||
index.train(big_npy)
|
||
faiss.write_index(index, trained_path)
|
||
|
||
log(i18n("[索引训练] 正在写入特征向量"))
|
||
starts = list(range(0, big_npy.shape[0], 8192))
|
||
for batch_index, start in enumerate(starts):
|
||
index.add(big_npy[start : start + 8192])
|
||
if should_report(batch_index, len(starts), 10):
|
||
log(
|
||
i18n("[索引训练] 写入进度:%s/%s")
|
||
% (batch_index + 1, len(starts))
|
||
)
|
||
|
||
added_name = "added_IVF%s_Flat_nprobe_%s_%s_%s.index" % (
|
||
n_ivf,
|
||
index_ivf.nprobe,
|
||
exp_name,
|
||
version,
|
||
)
|
||
added_path = os.path.join(exp_dir, added_name)
|
||
faiss.write_index(index, added_path)
|
||
log(i18n("[索引训练] 成功构建索引:%s") % added_name)
|
||
link_added_index(added_path)
|