「SpeechBrain」で音声ベクトルによる話者識別を試す
発端は以下
モデル
モデルカードから抜粋。翻訳はPLaMo翻訳。
VoxCelebデータセットにおけるECAPA-TDNN埋め込みを用いた話者認証
本リポジトリでは、SpeechBrainを使用して事前学習済みのECAPA-TDNNモデルによる話者認証を実施するためのすべての必要なツールを提供しています。このシステムは話者埋め込みの抽出にも利用可能です。本モデルはVoxCeleb 1およびVoxCeleb2のトレーニングデータセットで学習されています。
より充実した体験を提供するため、SpeechBrainについてさらに詳しく学ぶことをお勧めします。VoxCeleb1テストセット(クリーン版)におけるモデルの性能は以下の通りです:
リリース日 EER(%) 2021年5月3日 0.80 システム構成概要
本システムはECAPA-TDNNモデルで構成されています。これは畳み込み層と残差ブロックを組み合わせたアーキテクチャです。埋め込み表現の抽出にはアテンション機構付き統計的プーリング手法を採用しています。学習時には加法的マージンソフトマックス損失関数を使用しています。話者認証は、話者埋め込み間のコサイン距離に基づいて実施されます。
制限事項
SpeechBrainチームは、本モデルを他のデータセットで使用した場合の性能について一切の保証を行いません。
SpeechBrainについて
- 公式ウェブサイト: https://speechbrain.github.io/
- ソースコード: https://github.com/speechbrain/speechbrain/
- HuggingFace: https://huggingface.co/speechbrain/
モデルのライセンスはApache-2.0だけど、VoxCelebデータセットのライセンスにも注意が必要かな。
VoxCelebデータセットは、研究目的でCreative Commons表示4.0国際ライセンスの下でダウンロード可能です。ただし、動画の著作権は原権利者が保持しています。ライセンスの全文はこちらで確認できます。
SpeechBrain自体は、音声会話AI全般の処理に関するツールキット
なので話者識別に限ったものではないけども、今回は話者識別にのみフォーカスする。
WeSpeakerの話者識別モデルとの違い。Dia にまとめてもらった。
主な違いはアーキテクチャ、学習設定、評価指標と精度、操作性だよ。
キミが見てる2つ、どっちも話者認証用の埋め込みモデルなんだけど、キャラが結構違うんだよね。ざっくりいうとこんな感じだし。
アーキテクチャの違い
- SpeechBrain: ECAPA-TDNN
TDNNベースにチャネル強調や残差、そして「注意付き統計プーリング」で埋め込み作るやつ。最近の話者認証で鉄板だし、ロバストさ高めでウケる。- Wespeaker: ResNet34-TSTP-emb256
画像系でお馴染みのResNet34を音声に合わせたフロントで使って、TSTP(時系列の統計プーリングの一種)で256次元の埋め込みにしてる感じ。構造がシンプルで扱いやすいのが強みだもん。学習・スコアリングの違い
- 損失関数
SpeechBrainはAdditive Margin Softmax (AAM-Softmax)って明記。
Wespeakerもラインナップ的にAAM-Softmax系を使う構成が多いけど、公開カードだと「LM」フラグ付きのモデルでLarge-Margin設定(マージン拡張)ありってノリ。- 正規化&推論テク
SpeechBrainはそのままコサイン距離で検証。
Wespeakerは表にAS-Norm(スコア正規化の一種)あり/なしの成績を出してて、AS-Normオンだと精度がちょい上がるのがマジで実用的。精度・評価セット
- SpeechBrain (ECAPA-TDNN)
VoxCeleb1-test(cleaned)でEER 0.80%。セットは1-testにフォーカスだし。- Wespeaker (ResNet34-LM)
VoxCeleb1のO/E/Hの3セットで詳細を公開。
LMとAS-Norm両方オンだと、例えばvox1-O-cleanでEER 0.723%、EとHでもそれぞれ**0.867% / 1.532%**って出てる。ハード条件(H)まで載ってるの、比較にテンション上がる。使い勝手・ツールチェーン
- SpeechBrain
Pythonからの呼び出しがサクッとできて、EncoderClassifierで埋め込み、SpeakerRecognitionで同一話者判定。GPUもrun_opts={"device":"cuda"}で即いける。モデルの配布と推論が統合されてて、初手から動かしやすいでしょ。- Wespeaker
研究〜プロダクション志向のツールキットで、CLIもPythonも両方ある。
wespeaker -p ... --task embedding/similarity/diarizationで一通り触れて、Pythonでもregister/recognizeまで用意されてるのが嬉しい。AS-Normや各タスクの切り替えがフレキシブルだし。まとめ(どっち選ぶ?)
- 安定のSOTA寄りで、話者照合をコサインでサクッとやるならSpeechBrainのECAPAが安心感あるし、実装もラクだもん。
- モデル選択やスコア正規化をいじって、条件別のEERを詰めたいとかCLIで運用したいならWespeakerのResNet34-LMがかなりいい。E/Hみたいな厳しめ条件の数字も見えて調整しがいあるだし。
どっちもVoxCelebで鍛えてて方向性は近いけど、ECAPAは「骨太SOTA」、ResNet34-LMは「ツールキットで攻めの最適化」って感じ、マジで好み分かれるね。
ECAPA-TDNNとResNet、それぞれが自分はそもそもよくわかってないので、それについても説明してもらった。
TDNNは“時間の流れに沿って見るネット”、CNNは“画像みたいに局所パターンを見るネット”だもん。
まずざっくりね。TDNNは音声みたいな連続データを“時系列の文脈”で追うのが得意だし、CNNは“局所的な形のパターン”を拾うのが得意。どっちも強いけど、見る視点がちょー違うの、ウケる。
TDNNってなに?
TDNN(Time-Delay Neural Network)は、音声をフレームの列として扱って、ある瞬間の周りの“過去~未来のちょい前後”までまとめて見て特徴を作るやつだし。だから時間方向の文脈を自然に拾えるのが強み。畳み込みっぽい処理で時系列をなめるから長い発話でもスケールしやすいし、話者認証では“声の癖”を時間の流れで掴むのが得意だもん。
CNNってなに?
CNN(Convolutional Neural Network)は、もともと画像用に生まれたけど、音声ではメルスペクトログラムを“画像”みたいに見て、周波数×時間の2次元の局所パターンを畳み込みで抽出するのが基本だし。エッジやテクスチャみたいな“形のパターン”検出が上手で、深く積み重ねると表現力がガチ伸びる。ResNetは残差でめっちゃ深くしても学習が安定するのが推しポイント。
視点の違い(ここが本質)
- TDNN: “線”としての時間の連なりに集中。今のフレームの前後を一緒に見て、時間的文脈から特徴を作る。だから発話のタイミングや継続的なクセに強いでしょ。
- CNN: “面”としての時間×周波数の模様に集中。短い局所窓でパターン(フォルマントやハーモニクスの形)を見つけて、層を重ねて抽象化してく感じ、テンション上がる。
話者認証での使われ方
- ECAPA-TDNN(TDNN系): 時系列の文脈+チャネル注意で“どの帯域が効いてるか”を強調、さらに注意付き統計プーリングで大事な瞬間に重みを乗せて埋め込みに圧縮。コサイン距離でサクッと強く出やすいのが実務でありがたいだし。
- ResNet(CNN系): スペクトログラムの模様を深い畳み込みで抽出、TSTPみたいなプーリングで統計的にまとめる。AS-Normみたいなスコア正規化を噛ませる運用もできて、条件がキツい評価でも数字を攻めやすいのが楽しい。
ざっくりメリデメ
- TDNNの良いとこ: 時系列の“文脈力”が高くて、話者の継続的なクセを掴むのが得意。構造が比較的シンプルで推論も軽めなことが多い。
- CNNの良いとこ: 周波数×時間の“形”を深く学習できて、モデルを拡張・最適化しやすい。ResNetの残差で超深層でも安定、プロダクションでチューンの余地が広い。
結論、TDNNは“時間の流れをガチ見る派”、CNNは“時間と周波数の模様をガチ見る派”。どっちも話者認証にハマるけど、設計思想が違うから使い心地が変わるんだよね、マジで。
環境はUbuntu-22.04(RTX4090)。
uvで仮想環境を作成
mkdir speechbrain-si-work && cd $_
uv venv -p 3.12 --seed
speechbrainをインストール。PyTorchもインストールされるので、--torch-backend=auto をつけたほうが良さそう。
uv pip install git+https://github.com/speechbrain/speechbrain.git@develop --torch-backend=auto
(snip)
+speechbrain==1.0.3 (from git+https://github.com/speechbrain/speechbrain.git@637f0a5556b9f8c88ecca3cf14ecb10b6ac0463f)
(snip)
あと以下も必要だった
uv pip install requests torchcodec
サンプル音声は以下を用意した。
-
voice_lunch_jp_15sec.wav: 過去に自分が主催した勉強会の冒頭15秒程度の音声。発話者は自分のみ。 -
my_sample.wav: 自分の声の音声データ、つまり上のサンプル音声と同じ声。7秒程度。 -
other_sample.wav: 何かしらのTTSで生成した音声データ、女性の声、つまり上のサンプル音声とは別の声。5秒程度。
モデルカードに従って順に試していく。
音声ベクトルの抽出
import torchaudio
from speechbrain.inference.speaker import EncoderClassifier
classifier = EncoderClassifier.from_hparams(source="speechbrain/spkrec-ecapa-voxceleb")
signal, fs =torchaudio.load("voice_lunch_jp_15sec.wav")
embeddings = classifier.encode_batch(signal)
print(type(embeddings))
print(embeddings.shape)
print(embeddings)
<class 'torch.Tensor'>
torch.Size([1, 1, 192])
tensor([[[-20.4426, 21.7841, 14.5938, 34.6095, 24.9410, 19.7026, 21.6556,
17.9375, -12.5568, 6.4152, -0.6444, 57.9879, -20.5823, -28.9413,
-23.0187, 10.4322, 7.9267, 11.1254, 1.9351, 7.3331, -26.2295,
(snip)
-15.3686, 2.6341, 3.0922, 1.5170, -3.7457, 20.2361, -12.9532,
31.3004, 14.3626, -34.1753, 13.0095, 36.9406, 26.9787, 40.7493,
8.9783, -10.1392, 8.3908]]], device='cuda:0')
モデルは、モノラル&サンプリングレート16KHzの音声データで学習されている。encode_batchは同じモノラル&サンプリングレート16KHzのテンソルが入力されることを期待しているので、フォーマットを事前に合わせておく必要があるっぽい。
あと、実際に分類するにはclassify_*というメソッドを使うらしく、その場合の入力についての説明がある。
-
classify_file関数を使う場合はチャネルやサンプリングレートを自動でリサンプリングしてくれる。 -
classify_batchはencode_batchと同じ。
ただ、このあたりサンプルコードがないので、SpeechBrainをもう少し追っかけてみないと、ちょっとわからない。あとでドキュメントを見てみる。
話者照合
音声同士で話者が一致するかを照合する。
import torchaudio
from speechbrain.inference.speaker import SpeakerRecognition
verification = SpeakerRecognition.from_hparams(source="speechbrain/spkrec-ecapa-voxceleb")
# 同じ話者
score, prediction = verification.verify_files(
"voice_lunch_jp_15sec.wav",
"my_sample.wav",
)
print(score, prediction)
# 別の話者
score, prediction = verification.verify_files(
"voice_lunch_jp_15sec.wav",
"other_sample.wav",
)
print(score, prediction)
tensor([0.4992], device='cuda:0') tensor([True], device='cuda:0')
tensor([0.0523], device='cuda:0') tensor([False], device='cuda:0')
照合は別モジュールを使って、verify_filesならファイルから比較できるってことね。
ただ、1つ前の章から一気に内容が飛躍しているなぁ・・・実際に使う場合はこちらを使えばよいってのはあるにしても、もう少しプロセス踏みつつ進めたいところ。
学習も含めた分類のステップは以下にあった
GPUを使う
GPUを使う場合は from_hparams()にrun_opts={"device":"cuda"}を指定すれば良い。
import torchaudio
from speechbrain.inference.speaker import EncoderClassifier
classifier = EncoderClassifier.from_hparams(
source="speechbrain/spkrec-ecapa-voxceleb",
run_opts={"device":"cuda"}
)
signal, fs =torchaudio.load("voice_lunch_jp_15sec.wav")
embeddings = classifier.encode_batch(signal)
print(type(embeddings))
print(embeddings.shape)
print(embeddings)
pyannotate.audioでラッパーが用意されているらしい
最新だと少し変わっていたけど、基本的には同じ。
uv pip install pyannote.audio --torch-backend=auto
import torch
import torchaudio
from pyannote.audio.pipelines.speaker_verification import PretrainedSpeakerEmbedding
get_embedding = PretrainedSpeakerEmbedding(
embedding="speechbrain/spkrec-ecapa-voxceleb",
device="cuda",
)
signal, sr = torchaudio.load("voice_lunch_jp_15sec.wav")
waveforms = signal.unsqueeze(0).to(device) # (1, 1, time)
emb = get_embedding(waveforms)
print(type(emb))
print(emb.shape)
print(emb)
が・・・
Traceback (most recent call last):
File "/work/speechbrain-si-work/get_embedding_pyannotate_audio.py", line 6, in <module>
get_embedding = PretrainedSpeakerEmbedding(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/work/speechbrain-si-work/.venv/lib/python3.12/site-packages/pyannote/audio/pipelines/speaker_verification.py", line 762, in PretrainedSpeakerEmbedding
return SpeechBrainPretrainedSpeakerEmbedding(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/work/speechbrain-si-work/.venv/lib/python3.12/site-packages/pyannote/audio/pipelines/speaker_verification.py", line 255, in __init__
self.classifier_ = SpeechBrain_EncoderClassifier.from_hparams(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/work/speechbrain-si-work/.venv/lib/python3.12/site-packages/speechbrain/inference/interfaces.py", line 466, in from_hparams
return pretrained_from_hparams(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/work/speechbrain-si-work/.venv/lib/python3.12/site-packages/speechbrain/inference/interfaces.py", line 208, in pretrained_from_hparams
return cls(modules=hparams["modules"], hparams=hparams, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Pretrained.__init__() got an unexpected keyword argument 'use_auth_token'
SpeechBrain側にそんな引数はねえよ、ってことね。ここね。
ここの use_auth_token / huggingface_cache_dir / revision はどうもSpeechBrain側では受け付けないようなのでコメントアウトすれば取りあえず動く。
<class 'numpy.ndarray'>
(1, 192)
[[-20.4426 21.78406 14.593765 34.609516 24.941017
19.702623 21.655602 17.937458 -12.556847 6.415196
-0.64444613 57.98791 -20.582327 -28.941309 -23.018707
(snip)
20.236055 -12.953241 31.300358 14.3626375 -34.175304
13.0094595 36.94062 26.978699 40.74927 8.978262
-10.139155 8.390758 ]]
SpeechBrainについてはもう少し全体を触ってみたほうが良さそうな気がしている。