【Python】声優100人をキレイに話者識別するための機械学習レシピ

[とあるプログラミング学習サイト](https://paiza.jp/works/ai_ml/primer/ml-challenge2)で以下のような講座を見つけました。

>## <font color="green">Python×AI・機械学習入門編2: 話者認識をしよう</font>
><font color="green">機械学習で音声認識を行います。音声データから特徴量を抽出する方法を学習し、SVMで分類します。</font>

とても面白そうな記事ですが、**残念ながら有料プランの講座**でした。
概要とコードをチラ見したところ、<font color="red">**3人**</font>の声優を話者識別するために音声データから特徴量（MFCCなど）を抽出して、<font color="red">**SVM**（サポートベクターマシン）</font>で分類しているようです。

Google検索してみると、話者識別の記事のほとんどがSVMを用いていますが、
<font color="blue">**「SVMは本当にベストプラクティスなのか」
「声優を100人に増やしても、高い予測精度を維持できるか」**</font>
を中心に（もちろん無料の）記事を書いていきます。

▼ [動画](https://www.youtube.com/watch?v=bsGryIWIEqA)の冒頭を見ていただけると、タスクの難易度と概要が分かります
<iframe width="560" height="315" src="https://www.youtube.com/embed/bsGryIWIEqA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 0. 話者識別とは
話者認識（Speaker Recognition）の１つです。

```mermaid
graph LR;
    話者認識-->話者照合;
    話者認識-->話者識別;
```
**話者照合**（Speaker Verification）... ある人物が本人の主張している通りの個人であるかを照合する
<font color="red">**話者認識**</font>（Speaker Identification）... 誰だかわからない声を誰のものか識別する

ということだけ抑えておけば大丈夫そうです。
この記事では、<font color="blue">誰だか分からない声から声優を識別</font>する後者を扱います。


# 1. 学習データの収集

有料プランの講座でもなければ、声優でもないので、学習用のデータセットは自分で用意しなければなりません。
ふさわしいデータセットを検索していたら、

[JVS(Japanese Versatile Speech) corpus](https://sites.google.com/site/shinnosuketakamichi/research-topics/jvs_corpus)

という、**東京大学の猿渡研究室**が**無償で公開**している音声コーパスを見つけました。
[日本声優統計学会](https://voice-statistics.github.io/)が独自に構築した音素バランス文を、<font color="red">100人のプロフェッショナル話者（声優・俳優など）が読み上げた音声</font>（`parallel100`）が含まれています。

今回は、この`parallel100`を使って学習を行います。


# 2. 特徴量の抽出

いよいよ音声データから特徴量を抽出します。
特徴量には、音声認識の分野では馴染みが深いとされている<font color="red">**MFCC**（メル周波数ケプストラム係数）</font>を用います。

![original_and_mel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832523/658cf87f-a140-bc51-0aaa-ac9350fa160a.png)


**「メル尺度」** という人間の聴覚に基づいた尺度がポイントになります。
変換手順も含めて以下の記事を参考にさせていただきました。
[MFCC（メル周波数ケプストラム係数）入門 \- Qiita](https://qiita.com/tmtakashi_dist/items/eecb705ea48260db0b62)
（すごく分かりやすい記事なので、見るだけで感動します。オススメです！！）

原理に沿って計算するとステップはやや多いですが、Pythonでは[Librosa](https://librosa.org/doc/0.9.1/index.html)というライブラリを使って**簡単に**各フレームのMFCCを求めることができます！

```Python
import os
import numpy as np
import pandas as pd
import librosa

X_data = []  # 特徴行列
y_data = []  # クラスラベルデータ

for speaker_num in range(1, 101):  # 声優No.1~100
    # parallel100の音声データが入っているディレクトリ名
    dir_name = f'../input/jvs-ver1/jvs_ver1/jvs{str(speaker_num).zfill(3)}/parallel100/wav24kHz16bit'
    for file_name in os.listdir(dir_name):
        file_path = os.path.join(dir_name, file_name)  # 音声ファイルへのパス
        y, sr = librosa.load(file_path)  # 音声ファイルを読み込む
        mfcc = librosa.feature.mfcc(y, sr)  # MFCC
        mfcc = np.average(mfcc, axis=1)  # 時間平均を取る
        mfcc = mfcc.flatten()
        mfcc = mfcc.tolist()
        mfcc = mfcc[1:13]  # 低次の係数を取り出す（12次まで取り出すことが多い）
        X_data.append(mfcc)
        y_data.append(speaker_num)

X = pd.DataFrame(X_data, columns=[f'mfcc_{n}' for n in range(1, 13)])
y = pd.DataFrame({'target': y_data})

df = pd.concat([X, y], axis=1)
df.to_csv('mfcc.csv', index=False)  # csvで保存
df.head()
```
すると、このように１つの音声ファイルにつき<font color="red">12個の特徴量</font>が得られます。
![スクリーンショット 2022-06-08 000116.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832523/3c8a5dad-df95-8ced-41a6-d16b84945960.png)
（データセットも含めて）使用した**Kaggle Notebook**は[こちら](https://www.kaggle.com/code/alvinvin/adumaru-speaker-identification-1)で公開しています。


# 3. モデルの学習・予測
分類モデルにSVM（サポートベクターマシン）を使っている記事が多いですが、ドメイン知識を持っているわけではないので、とりあえず[PyCaret](https://pycaret.org/)でモデル比較を行います。

【参考】[【5分で分かる】PyCaretについて徹底解説！｜スタビジ](https://toukei-lab.com/pycaret)

<details>
<summary>事前準備</summary>
<div>

ライブラリのインストール
```Python
import numpy as np
import pandas as pd
from pycaret.classification import *
```
データセットを読み込む
```python
data = pd.read_csv('../input/100-speakers-mfcc/mfcc.csv')  # 保存したcsvファイルへのパス
data['target'] = data['target'] - 1  # クラスラベルを1ずらす
data.head()
```
</div>
</details>

データの前処理はたった１行で完結します。
```Python
reg = setup(data=data, target='target', data_split_shuffle=True, use_gpu=True, silent=True, fold=5, n_jobs=-1)
```
モデリングもたった１行で完結します。
```Python
compare_models()
# compare_models(exclude=['catboost', 'xgboost', 'gbc', 'rf'])  # 極端に遅いモデルを避ける書き方
```
![スクリーンショット 2022-06-08 002544.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832523/ae895f67-5407-2060-5524-c0e5d2f39a95.png)
どの評価指標においても、**qda**（[Quadratic Discriminant Analysis](https://qiita.com/mzmz__02/items/03b0ba6d7ebee7a016c2)）がトップでした。
<font color="red">正解率97.98%</font>と、想像よりはるかに良い精度が出ました。
このモデルをチューニングして、予測結果を出します。
```Python
lda = create_model('lda', verbose=False)
tuned_qda = tune_model(qda, optimize='AUC')
predict_model(tuned_qda)
```
![スクリーンショット 2022-06-08 003444.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832523/c4d9e210-7429-78a4-895d-5ffe29fdcc86.png)
なんと、<font color="red">**正解率98.57%**</font>です！
声優100人の話者識別問題に、ほぼ全問正解することができます。

:::note warn
ちなみに冒頭で紹介した（非線形）**SVM** は、PyCaretのモデル比較リストには入っていませんでしたが、`'rbfsvm'`で直接モデル指定して予測したところ正解率は **約78%** でした。100クラスの分類には向いていないのかもしれません。
:::

（データセットも含めて）使用した**Kaggle Notebook**は[こちら](https://www.kaggle.com/code/alvinvin/adumaru-speaker-identification-2)で公開しています。

# 4. さいごに
精度が良すぎてビックリしてしまいました。
[JVS(Japanese Versatile Speech) corpus](https://sites.google.com/site/shinnosuketakamichi/research-topics/jvs_corpus)には、`parallel100`の他にも**裏声**や**ささやき声**の音声データも入っているので、これらも識別できれば面白いなと思いました。
<font color="red">98.57%</font>をたたき出したモデルが間違えた問題を[動画](https://www.youtube.com/watch?v=bsGryIWIEqA)の最後に紹介しているので**皆さんでAIに勝てるかどうか**、ぜひ挑戦してみてください！！