声優の声を分類してみた


機械学習の練習に声優の声を識別させてみた。その手順をメモ書きとして記しておく。
###やりたいこと
まずは声優の素の声（ラジオとかの声の事）を学習させて、テストデータとしても素の声を入れてどれくらいの精度で判別できるかを確かめる。
次に、声優の素の声からアニメの声が予測できるのかどうかを試したい。
###1.まず、データを集める
今回はLadyGo声優５人について、ニコニコ動画でラジオの動画をダウンロードした。
このままでは.mp4なのでこれをwavに変換する。

```
ffmpeg -i hoge.mp4 -map 0:1 hoge.wav 
```

これでOK！

そして、このwavファイルを30秒ずつに分割した。

```
sox hoge.wav hogehoge.wav trim 0 30 : newfile : restart
```

まだ終わらない。
ここから手作業でコーナーの切り替わりシーンの部分とか、音楽流すコーナーとか、他の人の声が入ってる部分とかを手作業で削除していった。
ただ、バックで小さな音楽が常に流れているのはもうどうしようもないので無視。
このバックの音の影響を最小限、もしくは無くすにはどうすればいいんだろうか・・。

とりあえず、これでデータ収集は完了！
###2.データから特徴量を抽出する
周波数強度を特徴量にしたらいいんじゃない？高速フーリエ変換だ！となるが、
オライリーから出てる実践機械学習システムにはそれよりメル周波数ケプストラム係数(MFCC)ってやつを使った方がいいよ！と書いてたので今回はそっちを使うことにする。

色々見てみたところ、現在の音声認識ではMFCCが代表的な特徴量として用いられていて、人間の音声知覚の特徴を考慮してるらしい。
しかし、MFCCにはピッチの情報が含まれないようだ。
ケプストラムならピッチ成分を抽出できるみたいなのでもしかしたらこっちの方が特徴量としてはいいのかもしれない、が取り敢えずMFCCを使うことにする。

MFCCを計算するにはTaklbox Scikitというライブラリを用いる。
計算した結果をキャッシュとして保存して、それを使いまわしする。

```python
from scikits.talkbox.features import mfcc
import scipy
from scipy import io
from scipy.io import wavfile
import glob
import numpy as np
import os

def write_ceps(ceps,fn):
	base_fn,ext = os.path.splitext(fn)
	data_fn = base_fn + ".ceps"
	np.save(data_fn,ceps)

def create_ceps(fn):
	sample_rate,X = io.wavfile.read(fn)
	ceps,mspec,spec = mfcc(X)
	isNan = False
	for num in ceps:
		if np.isnan(num[1]):
			isNan = True
	if isNan == False:
		write_ceps(ceps,fn)

```

なんか、io.wavfile.read(fn)でうまく読み込めないwavファイルがあるよう、原因はよくわからなかった。。
うまく読み込めないままMFCCを計算すると結果がNanになるのでこれを無視するようにしている。

セーブしたデータを読み込むコードは以下。name_listはディレクトリ名のリスト。今回はLadyGo声優5人の名前のディレクトリを作成してそこにデータを保存してある。

```python
def read_ceps(name_list,base_dir = BASE_DIRE):
	X,y = [],[]
	for label,name in enumerate(name_list):
		for fn in glob.glob(os.path.join(base_dir,name,"*.ceps.npy")):
			ceps = np.load(fn)
			num_ceps = len(ceps)
			X.append(np.mean(ceps[:],axis=0))
			y.append(label)
	return np.array(X),np.array(y)

```
###3.学習させて予測する
2で作成した特徴量を用いて学習させる、今回はサポートベクターマシン(SVM)を用いて分類した。
コードは以下。

```python
import MFCC #これは2で作ったもの
from matplotlib.pyplot import specgram
from sklearn.metrics import confusion_matrix
from sklearn.svm import LinearSVC
from sklearn.utils import resample
from matplotlib import pylab
import numpy as np

name_list = ["Uesaka_Sumire","Komatsu_Mikako","Okubo_Rumi","Takamori_Natsumi","Mikami_Shiori"]

x,y = MFCC.read_ceps(name_list)
svc = LinearSVC(C=1.0)
x,y = resample(x,y,n_samples=len(y))
svc.fit(x[150:],y[150:])
prediction = svc.predict(x[:150])
cm = confusion_matrix(y[:150],prediction)
```

データを読み込んできた後、それらをresample()によってシャッフルし、151個目以降のデータを教師データ、150個目までのデータをテストデータとしてみた。

confusion_matrixっていうのは混合行列。
テストデータに対して予測した結果のラベルごとの分布を示している。
これを用いてグラフをプロットしてみる。
###4.グラフをプロットしてみる
3で得た予測結果の混合行列を用いて正答率をプロットしてみる。
正答率を出すために、混合行列の値を正規化する。

```python
def normalisation(cm):
	new_cm = []
	for line in cm:
		sum_val = sum(line)
		new_array = [float(num)/float(sum_val) for num in line]
		new_cm.append(new_array)
	return new_cm
```

グラフをプロットする。

```python
def plot_confusion_matrix(cm,name_list,name,title):
	pylab.clf()
	pylab.matshow(cm,fignum=False,cmap='Blues',vmin=0,vmax=1.0)
	ax = pylab.axes()
	ax.set_xticks(range(len(name_list)))
	ax.set_xticklabels(name_list)
	ax.xaxis.set_ticks_position("bottom")
	ax.set_yticks(range(len(name_list)))
	ax.set_yticklabels(name_list)
	pylab.title(title)
	pylab.colorbar()
	pylab.grid(False)
	pylab.xlabel('Predict class')
	pylab.ylabel('True class')
	pylab.grid(False)
	pylab.show()
```
結果。

![スクリーンショット 2015-06-23 1.34.50.png](https://qiita-image-store.s3.amazonaws.com/0/44218/0274e4ba-4b62-2826-a862-d874502f7b7b.png "スクリーンショット 2015-06-23 1.34.50.png")

正答率100％、何度やってもほぼ100％だった。

次にアニメ声をテストデータとして試してみる。
ただ、データを集めるのが難しく、各声優に対して1キャラもしくは2キャラくらいしか集められなかった、データ数自体も少ない（5人合わせて90パターンくらい）。

結果は以下。

![素の声からアニメの声を予測.png](https://qiita-image-store.s3.amazonaws.com/0/44218/8a233309-e7b1-fe00-88e9-10150057b079.png "素の声からアニメの声を予測.png")

全然ダメだった！

更に、アニメの声を教師データとして素の声をテストしてみた。

![アニメの声から素の声を予測.png](https://qiita-image-store.s3.amazonaws.com/0/44218/5ecf2b31-bc97-5a10-1ca7-706b8769dee6.png "アニメの声から素の声を予測.png")

勿論これもダメ。

###5.考察と検証
アニメ声を判別した際に非常に精度が悪くなった理由として、過学習が思いつく。
mfcc()関数のデフォルトでは13個の係数が音声のフレーム個だけある、これでは非常に多いので全フレームで各係数を平均した値を特徴量としている。
つまり、特徴ベクトルの数は13個、これが原因で過学習したとは考えにくい。

そこで、利用したデータに偏りがあったのが汎化能力の低下に繋がったのではないだろうか、と考えた。
利用したデータは全てラジオ内の声である為、各声優において含まれる周波数成分に差があまり見られず、それが汎化能力の低下に繋がったのではないだろうか。

そこで、適当なアニメ声の一部を教師データに加えて、残りのアニメ声に対してテストしてみた。
教師データは2/3がラジオの声、1/3がアニメの声程度の割合。

まずはラジオの声をテストしてみた。
![スクリーンショット 2015-06-23 2.40.45.png](https://qiita-image-store.s3.amazonaws.com/0/44218/e817c752-9404-0574-180e-e609ab99f530.png "スクリーンショット 2015-06-23 2.40.45.png")

認識精度は前回と同様ほぼ100％、これも何度やっても大体これくらいになる。

次に、アニメの声をテストしてみる。

![スクリーンショット 2015-06-23 2.40.59.png](https://qiita-image-store.s3.amazonaws.com/0/44218/07a6f83e-b84f-db81-8b3d-b0b8496151bd.png "スクリーンショット 2015-06-23 2.40.59.png")

認識精度が飛躍的に高まった！
やはりラジオの声のみで学習していたせいで、汎化能力が非常に低下してしまっていたようだ。

###6.結論
機械学習においては、過学習の危険性について常に考えなければならない事がわかった。
そしてその上で、汎化能力を正確に測定する必要がある。
そういう面では今回、正答率だけで分類器を評価してしまっているのはよくない。
クラス分類器を評価するには適合率ー再現率曲線やROC曲線を用いれば良い（って本に書いてあった）

###参考
オライリージャパン 実践機械学習システム