📖
Pythonで音声分類してみる ― MFCCを使ったシンプルな機械学習入門
Pythonで音声分類してみる ― MFCCを使った音声分類モデルをつくる(前半)
はじめに
ここまでの章で音声の特徴量(MFCC)を学びました。
この章では、MFCCを使って音声を分類するモデルを作ります。「この音は何の音か」を機械学習で判別します。
必要なライブラリ
import numpy as np
from scipy.signal import stft
from scipy.fft import dct
from scipy.io.wavfile import write
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
データの準備
分類する音声を用意します。自分で録音したwavファイルがある場合はそちらを使ってください。ここではテスト用の音声を生成します。
重要:分類が簡単すぎる条件は避ける
周波数が大きく異なる音(220Hz/440Hz/880Hz)を使うと、データが少なくても・特徴量がシンプルでも簡単に分類できてしまい、データ数や特徴量設計の効果が見えません。
ここでは以下の「難しい条件」を設定します。
- 周波数が近い(300Hz / 350Hz / 400Hz)
- ランダムな音量変化(エンベロープ)を加える
- 強めのノイズを混ぜる
- サンプルごとに周波数を±8%揺らす
sr = 16000
np.random.seed(42)
def make_voice_sample(base_freq, sr=16000, duration=1.0, seed=0):
"""
現実の音声に近い条件で音声を生成する
- 基本周波数をランダムに揺らす
- 時間変化するエンベロープを加える
- ノイズを混ぜる
"""
rng = np.random.RandomState(seed)
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
# 周波数を±8%の範囲でランダムに揺らす(話者の個人差を模擬)
freq = base_freq * (1 + rng.uniform(-0.08, 0.08))
# 倍音付きの信号
y = (np.sin(2 * np.pi * freq * t) +
0.7 * np.sin(2 * np.pi * freq * 2 * t) +
0.5 * np.sin(2 * np.pi * freq * 3 * t))
# ランダムなエンベロープ(音量の時間変化)
envelope = np.abs(np.sin(2 * np.pi * rng.uniform(1, 5) * t)) + 0.3
y = y * envelope
# ノイズを加える
y += rng.normal(0, 0.4, len(t))
return (y / (np.max(np.abs(y)) + 1e-8)).astype(np.float32)
# 3クラスの音声(周波数が近い)
classes = {"a": 300, "i": 350, "u": 400}
audio_data = {}
for name, freq in classes.items():
# 各クラス20サンプル(異なるseedで個体差を持たせる)
audio_data[name] = [make_voice_sample(freq, sr, seed=i) for i in range(20)]
print(f"各クラスのサンプル数: {len(audio_data['a'])}")
print(f"クラス間の周波数差: 50Hz(300/350/400Hz)")
音声分割と特徴量抽出
def split_audio(y, sr, duration=0.5):
"""音声を固定長に分割する"""
samples = int(sr * duration)
return [y[i:i+samples] for i in range(0, len(y)-samples+1, samples)]
def extract_mfcc_features(y, sr, n_mfcc=13, n_fft=512, hop=256):
"""MFCCの mean + std を特徴量として返す"""
_, _, Zxx = stft(y, fs=sr, nperseg=n_fft, noverlap=n_fft-hop)
power = np.abs(Zxx) ** 2
# メルフィルタバンク
n_mels = 26
def hz_to_mel(hz): return 2595 * np.log10(1 + hz / 700)
def mel_to_hz(mel): return 700 * (10 ** (mel / 2595) - 1)
mel_pts = np.linspace(hz_to_mel(0), hz_to_mel(sr/2), n_mels + 2)
hz_pts = mel_to_hz(mel_pts)
bins = np.floor((n_fft + 1) * hz_pts / sr).astype(int)
fb = np.zeros((n_mels, n_fft // 2 + 1))
for m in range(1, n_mels + 1):
for k in range(bins[m-1], bins[m]):
fb[m-1, k] = (k - bins[m-1]) / (bins[m] - bins[m-1] + 1e-10)
for k in range(bins[m], bins[m+1]):
fb[m-1, k] = (bins[m+1] - k) / (bins[m+1] - bins[m] + 1e-10)
mel_power = np.dot(fb, power)
log_mel = np.log(mel_power + 1e-10)
mfcc = dct(log_mel, type=2, axis=0, norm='ortho')[:n_mfcc]
return np.concatenate([np.mean(mfcc, axis=1), np.std(mfcc, axis=1)])
データセットの作成
ここが重要なポイントです。学習データとテストデータは必ず別のサンプルから作ります。
label_map = {"a": 0, "i": 1, "u": 2}
X_all, y_all = [], []
for name, samples in audio_data.items():
for y_audio in samples:
for seg in split_audio(y_audio, sr):
feat = extract_mfcc_features(seg, sr)
X_all.append(feat)
y_all.append(label_map[name])
X_all = np.array(X_all)
y_all = np.array(y_all)
print(f"全データ数: {len(X_all)}")
# 学習データ80% / テストデータ20% に分割
# stratify=y_all で各クラスの比率を維持する
X_train, X_test, y_train, y_test = train_test_split(
X_all, y_all,
test_size=0.2,
random_state=42,
stratify=y_all
)
print(f"学習データ数: {len(X_train)}")
print(f"テストデータ数: {len(X_test)}")
作成した学習モデルに関しての精度などの検証も必要です。
本章に関して、後半の内容は本に記載しました。
続きとして以下の内容を載せています。
●実験結果
精度
混同行列
●問題点
データ不足
特徴量が弱い
●改善
分割
mean + std
Discussion