10月29日、Armin Ronacherが「Custom Voices With Qwen3-TTS」と題した記事を公開した。Qwen3-TTSの音声埋め込みを活用してカスタム音声のクローン・ブレンド・感情制御を実現する実装手法を詳細に紹介しており、TTSモデルの内部表現を直接操作するという踏み込んだ内容になっている。
Armin Ronacherは子どもたちと一緒にダンジョンクローラーゲームを開発しており、ナレーター用の音声が必要になったことがこの調査のきっかけだ。「ありきたりなAI音声にはしたくない」という動機から、Qwen3-TTSの音声埋め込みを掘り下げた。
Qwen3-TTSの3モデル構成
Qwen3-TTSには用途の異なる3種類のモデルが存在する:
- **
1.7B-Base**(および0.6B):数秒の参照音声から音声をクローンする 1.7B-CustomVoice:9種類の組み込みスピーカーと、「悲しく話す」「囁く」といった自然言語インストラクションに対応- **
1.7B-VoiceDesign**:テキスト説明から音声を生成する
問題は、Baseモデルはクローンした音声にインストラクションを与えられない点だ。クローンと感情制御を同時に使いたい場合、公式には「BaseでファインチューニングするかVoiceDesignで生成したクリップをBaseでクローンする」しかなく、どちらの方法でも指示は使えなくなる。
音声埋め込みの算術:ブレンド・ジェンダー軸操作
記事の核心はここだ。Qwen3-TTSのBaseモデルが音声クローンで使うスピーカー埋め込みは、2048次元のベクトルに過ぎない。ベクトルである以上、算術演算が使える。
音声ブレンド
ArminはPyAI Confでの自分のトーク映像から28秒を切り取り参照音声とした。VoiceDesignで別の音声を生成し、両者の埋め込みを加重平均することで2つの声の中間的な声を作れる。
def blend(a, b, t):
a, b = a.float(), b.float()
mixed = (1 - t) * a + t * b
return (mixed / mixed.norm() * ((1 - t) * a.norm() + t * b.norm())).to(torch.bfloat16)
ブレンド比率を0〜1で動かすと11段階の中間音声が得られる。Arminによれば「自分の声らしさはすぐ薄れるが、いくつかの特徴は残り、ユニークな声になる」とのことだ。また、ラペルマイクとデザイン音声でスペクトログラムが違うため、録音環境が近い方がブレンドの品質は上がると注記している。
気に入ったブレンド結果は、一度音声として出力して再度フィンガープリントを取ることで、単一の参照ファイルとして保存できる。
ジェンダー軸に沿った音声操作
さらに踏み込んで、Arminはジェンダー軸を探索している。VoiceDesignで男性14種・女性14種を生成し、「平均女性 − 平均男性」のベクトルを計算。このベクトルに沿って任意の音声を移動させることで、声をより男性的・女性的な方向に動かせる。
middle = (women.mean(0) + men.mean(0)) / 2
axis = women.mean(0) - men.mean(0)
axis = axis / axis.norm()
def move(xvec, t):
x = xvec.float()
return (x + (t - (x - middle) @ axis) * axis).to(torch.bfloat16)
word2vecで有名になった「King − Man + Woman = Queen」的な発想を音声埋め込みに適用した格好だ。
本命:クローン音声へのインストラクション適用
記事で最も実用的なハックがこれだ。BaseとCustomVoiceはスピーカーを埋め込む方法がほぼ同じであることに着目し、Baseで取得したx-vectorをCustomVoiceに渡すことで、クローンした声にインストラクションを効かせることに成功している。
cv = Qwen3TTSModel.from_pretrained("Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice", **kw)
def say(text, xvec, instruct=None, language="English"):
ids = cv._tokenize_texts([cv._build_assistant_text(text)])
told = [cv._tokenize_texts([cv._build_instruct_text(instruct)])[0]
if instruct else None]
voice = {"ref_code": [None], "ref_spk_embedding": [xvec],
"x_vector_only_mode": [True], "icl_mode": [False]}
codes, _ = cv.model.generate(
input_ids=ids, instruct_ids=told, voice_clone_prompt=voice,
languages=[language], non_streaming_mode=True,
**cv._merge_generate_kwargs(temperature=0.7, subtalker_temperature=0.7),
)
wavs, rate = cv.model.speech_tokenizer.decode([{"audio_codes": codes[0]}])
return wavs[0], rate
「Speak in a sad, sorrowful tone, voice low and heavy, on the verge of tears.」のようなインストラクションをクローン音声に対して渡せる。組み込みスピーカーほど安定はしないが、感情によっては十分実用的な結果が出たとArminは評価している。
スピーカー識別への応用と限界
スピーカー埋め込みを話者識別に使う試みも紹介されている。ポッドキャスト音声を2秒窓・1秒スライドで切り出し、埋め込みの類似度で話者を分類した実験だ。
結果は興味深い。Qwen3の埋め込みは、同一人物でも録音環境が違うと参照として使いにくい(別のポッドキャスト回の自己紹介20秒を参照にしたら98%の精度、カンファレンス映像からの参照はほぼ機能せず)。一方、話者認識専用モデルの**ECAPA-TDNN**はカンファレンス映像参照でも97%の精度を出した。
この差はTTSモデルの埋め込みの設計目的に由来する。TTSのスピーカー埋め込みは「この声で合成する」という指示を伝えるために最適化されており、マイクの種類・残響・周波数特性といった録音環境由来の音響条件を声質の一部として取り込んでしまう傾向がある。対してECAPA-TDNNのような話者認識専用モデルは、そうした音響条件の差異を吸収して話者固有の特徴だけを抽出するよう訓練されている。TTSモデルの埋め込みがそのまま高精度の話者認識に使えるわけではない、という実践的な知見だ。
セットアップ
使用パッケージはqwen-tts(バージョン0.1.1)。
pip install qwen-tts
モデルはMPS(Apple Silicon)、bfloat16、SDPAアテンションで動かしている。参照音声は28秒・カンファレンス映像という条件でも十分機能したと報告されており、クリーンな収録環境は必須ではない。
詳細はCustom Voices With Qwen3-TTSを参照していただきたい。