動かざることバグの如し

近づきたいよ 君の理想に

Pythonで話者分類を行ってみた

環境

やりたいこと

すでに分類されている話者の複数wave形式の音声ファイルを学習し、未知の音声ファイルを渡したときにどれが一番近いかを推論してほしい。

いわゆる話者分類ってやつ。

ライブラリインストール

このプロジェクトでは、音声ファイルの話者を識別するためにpyannote.audioscikit-learnlibrosaといったライブラリを使用する。これらのライブラリは以下のコマンドでインストールできる。

pip install pyannote.audio==3.3.1 scikit-learn==1.5.1 librosa==0.10.2.post1

コード

train.py

import os
import pickle

import librosa
from pyannote.audio import Inference, Model
from sklearn.preprocessing import LabelEncoder
from sklearn.svm import SVC

# pyannote/embeddingモデルを読み込む
model = Model.from_pretrained("pyannote/embedding", use_auth_token="YOUR_ACCESS_TOKEN")
inference = Inference(model, window="whole")

# 話者の音声ファイルが格納されているディレクトリ
voices_dir = "./voices"

embeddings = []
labels = []

# 各話者のディレクトリをループ
for speaker in os.listdir(voices_dir):
    speaker_dir = os.path.join(voices_dir, speaker)
    if os.path.isdir(speaker_dir):
        # 各音声ファイルをループ
        for file in os.listdir(speaker_dir):
            if file.endswith(".wav"):
                file_path = os.path.join(speaker_dir, file)
                print(file_path)
                duration = librosa.get_duration(path=file_path)
                # 一定秒数未満の音声ファイルはスキップ
                threshold_seconds = 2
                if duration < threshold_seconds:
                    print(
                        f"Skipping {file_path}: Duration is less than {threshold_seconds} seconds ({duration:.2f}s)"
                    )
                    continue
                # 音声ファイルから埋め込みベクトルを抽出
                embedding = inference(file_path)
                embeddings.append(embedding.squeeze())
                labels.append(speaker)

# ラベルをエンコード
le = LabelEncoder()
encoded_labels = le.fit_transform(labels)

# SVMモデルを訓練
svm = SVC(kernel="rbf", probability=True)
svm.fit(embeddings, encoded_labels)

# モデルと関連情報を保存
with open("voices.pkl", "wb") as f:
    pickle.dump({"svm": svm, "label_encoder": le}, f)

print("saved as voices.pkl")

predict.py

import pickle
import sys

from pyannote.audio import Inference, Model

# コマンドライン引数をチェック
if len(sys.argv) != 2:
    print("使用方法: python predict.py <音声ファイルのパス>")
    sys.exit(1)

# 音声ファイルのパスを取得
audio_file = sys.argv[1]

# pyannote/embeddingモデルを読み込む
model = Model.from_pretrained("pyannote/embedding", use_auth_token="YOUR_ACCESS_TOKEN")
inference = Inference(model, window="whole")

# 保存されたモデルを読み込む
with open("voices.pkl", "rb") as f:
    data = pickle.load(f)
    svm = data["svm"]
    le = data["label_encoder"]

# 新しい音声ファイルから埋め込みベクトルを抽出
embedding = inference(audio_file)

# embeddingを2D配列にリシェイプ (1 サンプル × N 特徴量)
embedding_reshaped = embedding.reshape(1, -1)

# 予測を行う
prediction = svm.predict(embedding_reshaped)
probabilities = svm.predict_proba(embedding_reshaped)

# 予測結果を人間が読める形式に変換
predicted_speaker = le.inverse_transform(prediction)[0]
confidence = probabilities[0][prediction[0]]

print(f"予測された話者: {predicted_speaker}")
print(f"信頼度: {confidence:.2f}")

実行

学習

以下のようにディレクトリを配置し、それぞれwaveファイルを置く

├── voices
│   ├── taro
│       ├── 1.wav
│       ├── 2.wav
        (以下略)
│   ├── hanako
│   ├── mike
(以下略)

で学習開始

python train.py

すると voices.pkl が作成される。

予測

あとは未知のファイルのパスを引数で渡すと

python predict.py unknown.wav
予測された話者: hanako
信頼度: 0.69

のように自動判別してくれる。やったね

使ったモデル、手法の解説

このプロジェクトでは、話者の音声ファイルから特徴を抽出し、それを元に話者を識別するモデルを訓練する。

具体的には、pyannote.audioembeddingモデルを使用して音声ファイルから埋め込みベクトルを抽出し、その埋め込みベクトルを元にSVMSupport Vector Machine)モデルを訓練する。

pyannote.audioembeddingモデルは、音声ファイルから話者の特徴を捉える埋め込みベクトルを抽出するためのモデルである。このモデルは大量の音声データを元に訓練されており、様々な話者の音声特徴を捉えることができる。

SVMは、高次元空間上での分類問題を解くためのアルゴリズムである。このプロジェクトでは、pyannote.audioembeddingモデルで抽出した埋め込みベクトルを元に、話者を識別するSVMモデルを訓練する。SVMモデルは、各話者の埋め込みベクトルが形成する分布を学習し、未知の音声ファイルがどの話者の分布に最も近いかを推論する。

このようにして、未知の音声ファイルの話者を識別する。この手法は、話者の数が多く、各話者の音声データが少ない場合でも高い性能を発揮する。また、新たな話者が追加された場合でも、その話者の音声データを元にモデルを追加訓練することで対応可能である。このような特性から、この手法は「話者分類」と呼ばれる。

参考リンク