アカデミック

【超初心者向け】pythonで音声認識⑦「ケプストラムを抽出してみよう」

pythonで簡単な音声認識をやってみたいぞ。

そもそも何から始めればいいのかしら。

今回は,基本的な音声認識をpythonで行う方法をお伝えしていこうと思います。本記事はpython実践講座シリーズの内容になります。その他の記事は,こちらの「Python入門講座/実践講座まとめ」をご覧ください。

【超初心者向け】python入門講座/実践講座まとめ目次 入門講座 1.実行環境2.文字の出力3.データ型4.変数5.更新と変換6.比較演算子7.論理演算子8.条件分岐9.リスト10.辞...

コーディングに関して未熟な部分がたくさんあると思いますので,もし何かお気づきの方は教えていただけると幸いです。また,誤りについてもご指摘していただけると非常に助かります。

お題

ケプストラムをpythonで抽出してみよう。

流れ

今回対象とするデータは,私が喋った「あいうえお」とします。音声データを貼り付けるのは,どこか恥ずかしさを覚えるため,代わりにスペクトログラムをお見せしておきます。しっかりスペクトロラムに5つの区分が見え,それぞれが「あ/い/う/え/お」に相当することが確認できます。

「あいうえお」のスペクトロラム

ケプストラムの概要

波形・スペクトラム・ケプストラムの関係

ケプストラムってなに?

ケプストラムは,周波数の周波数です。(少し語弊はあります)

はい?

戸惑うのも無理はないです。ゆっくりお伝えしていきます。ケプストラムの説明には2通りあります。1つめは「対数スペクトルを音声とみなしてフーリエ変換した特徴量」です。これが,先ほどの「周波数の周波数」という説明に繋がります。

もう1つは「対数スペクトルを逆フーリエ変換した特徴量」です。周波数領域のスペクトルを逆フーリエ変換した結果は,時間領域の波形に戻ります。しかし,周波数領域で対数をとっているため,元の波形に戻るわけではないのです。

Web上にはケプストラムの説明として,周波数領域から「フーリエ変換を施したもの」「逆フーリエ変換を施したもの」という2つの説明が氾濫しています。これらは本質的に変わりません。結局,スペクトル包絡を得たいというのがケプストラムのモチベーションです。まれに,対数スペクトルをフーリエ変換することが「間違い」だと勘違いされている方もいらっしゃるため,注意が必要です。

ちなみにケプストラム「Cepstrum」は「Spectrum」の頭4文字をひっくり返したネーミングになっています。

ケプストラムが表すモノ

結局スペクトラムは何を表すの?

スペクトラムは,ほとんどの場合その低次元部分が利用されます。この低次元部分は,スペクトラムのおおまかな構造を表しているため,「スペクトル包絡」と呼ばれることもあります。つまり,周波数帯の情報を「波形」と捉えて,その低周波数部分のみを抽出することで,波形の大まかな構造を捉えようというのがケプストラムのモチベーションです。

周波数領域のおおかな構造は,人それぞれの特性を表すとされています。ですので,音声認識ではケプストラムから抽出された特徴量というのは非常に重要な役割を果たします。

実装

必要なライブラリのインポート

import librosa
import numpy as np
import matplotlib.pyplot as plt

wavファイルの読み込み

# [path_to_wavfile]にはみなさんの環境に合わせたパスを入れてください
y, sr = librosa.load("[path_to_wavfile]")

plt.plot(np.arange(y.shape[0]), y)
plt.xlim(200, 1500)
plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)
plt.tick_params(bottom=False, left=False, right=False, top=False)
plt.xlabel("Time [s]")
plt.rcParams["font.size"] = 14

高速フーリエ変換

n = 2048
# フーリエ変換 + 振幅取得 + 対数変換
spectrum_amp_log = np.log(np.abs(np.fft.fft(y, n)))

plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)
plt.tick_params(bottom=False, left=False, right=False, top=False)
plt.xlabel("Frequency [Hz]")
plt.rcParams["font.size"] = 14

さらに高速フーリエ変換

cep = np.fft.fft(spectrum_amp_log)
plt.plot(x_axis[0:n//2], np.real(cep[0:n//2]))

低次元のみを取り出してスペクトル包絡抽出

dims = 100

# 今までは半分までしかが層に表示していなかったことに注意
# 逆変換には左右対称部分も突っ込まなければ辻褄が合わない
# 低次元を削るということは,それに対応する高次元部も左右対称に削らなければならない
cep[dims:cep.shape[0]-dims] = 0

plt.plot(x_axis[0:n//2], log_spectrum_amp[0:n//2])
plt.plot(x_axis[0:n//2],np.real(icep[:n//2]))
plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)
plt.tick_params(bottom=False, left=False, right=False, top=False)
plt.xlabel("Frequency [Hz]")
plt.rcParams["font.size"] = 14

ここでは,いままでフーリエ変換の結果のうち前半部分だけを可視化していたということに注意しましょう。逆フーリエ変換の入力には,左右対称のスペクトラムを突っ込まなければならないため,低次元部分を削ると同時に,それに対応する左右対称の高次元部分も削らなければなりません。(私はここでハマりました)

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です