読者です 読者をやめる 読者になる 読者になる

kivantium活動日記

プログラムを使っていろいろやります

第27羽 こころぴょんぴょんイントロクイズ

この記事はごちうさ住民 Advent Calendar 2014の第27日目の記事です。(またまた勝手に参加

ごちうさの魅力はいろいろありますが、最大の魅力の一つがオープニング曲でしょう。「こころぴょんぴょん」という魅惑の響きは多くの人を虜にしました。ごちうさ住民であればDaydream caféのイントロを一瞬聞くだけで、脊髄反射でこころぴょんぴょんし始めることでしょう。

コンピュータは心を持たないと言われていますが、きっとどこかでこころぴょんぴょんしたいと思っているに違いありません。そこで、オープニングを聞いたらこころぴょんぴょんするプログラムを書いてみました。

ひと目で尋常でない難民キャンプだと見抜いたよ

ニコニコ大百科の難民の項目によれば、難民たちの流浪の旅はゆゆ式に始まり、きんいろモザイクのんのんびより桜Trick未確認で進行形を経由して約束の地「ご注文はうさぎですか?」に辿り着いたとされています。

そこで、これらの難民キャンプアニメにまんがタイム系のアニメをいくつか加えた10曲のイントロを聞いてDaydream caféだけに反応してこころぴょんぴょんするプログラムを書くことにします。

具体的には、

の10曲を判別します。

特徴量を取るお話

判別を行うためには各曲のデータから特徴量を抜き出す必要があります。類似楽曲検索システムを作ろうを参考にMFCCという特徴量を使いました。

SPTKのインストール

MFCCを使うためにMFCCというライブラリをインストールします。環境はUbuntu 14.04です。
まず、公式サイトからtar.gzファイルをダウンロードし、解答します。

./configure
make
sudo make install

なお、僕の環境ではcshをインストールする必要がありました。

データの準備

各曲の音源を用意し、Audacityなどのソフトウェアで冒頭の5秒程度を抜き出して正規化を行ったデータを作ります。データの最初の方に無音時間がないように注意します。その後、それぞれのデータに対して次のシェルスクリプトCSVファイルを作ります。(print_mfcc.pyはSPTKの使い方 (6) MFCCの抽出にあるPythonスクリプトを保存して使ってください)

#!/bin/bash
lame --resample 16 -b 32 -a $1.mp3 temp.mp3
lame --decode temp.mp3 temp.wav
sox temp.wav temp.raw
bcut +s -s 1 -e 48000 < temp.raw > temp2.raw
x2x +sf < temp2.raw | frame -l 400 -p 160 | mfcc -l 400 -f 16 -n 40 -m 19 -E > temp.mfc
python print_mfcc.py temp.mfc 20 > $1.csv
rm temp.mp3 temp.wav temp.raw temp2.raw temp.mfc

mfcc.shという名前で保存し、実行権限を与えます。
例えば

./mfcc.sh gochiusa

とすれば、gochiusa.mp3から冒頭3秒のMFCCを抜き出したgochiusa.csvが生成されます。この処理を各曲に対して行います。

シェルスクリプトが何をやっているのか気になる方は類似楽曲検索システムを作ろうをご覧ください。ざっと説明すると、mp3を16kHzでリサンプリングし、48000サンプルを切り出して160サンプルごとにMFCCを計算してCSVに出力しています。1サンプルごとに20の特徴に変換されるので、冒頭3秒分のデータを6000次元のベクトルに変換していることになります。

Vimなどを使って各CSVファイルのタブ及び改行を全てカンマに変換し、こころぴょんぴょんする曲に対しては最後の列にラベル1を、それ以外の曲にはラベル0を付加します。

cat *.csv > output.csv

として、加工した10個のCSVファイルを一つのCSVファイルにまとめます。
(このあたりはprint_mfcc.pyを書き換えればよりスマートにできるはずです)

対クラス分類用最終兵器 ニューラルネットワーク

このCSVファイルからニューラルネットワークで分類器を作ります。言語はもちろんこころぴょんぴょんするあの言語です。NNetwork.hはC++による多層パーセプトロンの実装に書いたものを使いました。

#include <iostream>
#include <cstdio>
#include "mlp.h"
using namespace std;

#define DATA_SIZE 10
#define VEC_SIZE 6000
int main(void){
    //入力2 隠れ3 出力3のニューラルネットワークを生成
    mlp net(VEC_SIZE,10,2);
    //訓練データ
    float x[VEC_SIZE*DATA_SIZE];
    //教師データ
    int t[DATA_SIZE];
    char filename[100];
    //CSVの読み込み
    FILE *fp = fopen("output.csv", "r");
    if(fp==NULL) return -1;
    for(int i=0; i<DATA_SIZE; i++){
        for(int j=0; j<VEC_SIZE; j++){
            fscanf(fp, "%f,", x+VEC_SIZE*i+j);
        }
        fscanf(fp, "%d\n", t+i);
    }
    //訓練
    net.train(x, t, DATA_SIZE, 1000);
    cout << "Enter file name" << endl;
    while(cin >> filename){
        fp = fopen(filename, "r");
        if(fp==NULL){
            cout << " File not exist" << endl;
            continue;
        }
        for(int i=0; i<VEC_SIZE; i++){
            fscanf(fp, "%f,", x+i);
        }
        //結果の表示
        if(net.predict(x) == 1) cout << "あぁ^~心がぴょんぴょんするんじゃぁ^~" << endl;
        else cout << "こころぴょんぴょんしません (´・ω・`)" << endl;
    }
    return 0;
}

結果はこんな感じです。
f:id:kivantium:20141226211809p:plain:w500
それぞれの曲から生成したCSVファイルを与えるとgochiusa.csvの場合だけこころぴょんぴょんし、それ以外の曲にはこころぴょんぴょんしないプログラムが出来ました。

終わりに

この分類方法だと、音源からの切り出し方法によって全く違う結果になってしまう可能性が非常に高いです。また、MP3ではなく実際にマイクから拾った音源を与えると恐らくほとんど機能しないでしょう。何らかの方法でノイズに強く、切り出した部分に依存しない特徴量を取る必要があります。

そもそもこのネタをやろうと思ったのは、ご注文は機械学習ですか? - kivantium活動日記
で行った画像に対する処理を音声に対してもやってみたかったという動機なので、特徴量の抜き出し以外はほとんど同じ手法を使っています。

音声認識に詳しい方からのツッコミをお待ちしています。


それではこれからもごちうさ界隈が楽しいものであることを祈って、結びとさせていただきます。