5
6

More than 5 years have passed since last update.

【音声学習】AutoencoderとLSTMで母音を深層学習して遊んでみる♬

Last updated at Posted at 2019-08-05

深層学習で音声の再生は格段に精度が上がったという。
そこで、今回は「あ、え、い、お、う」の母音を録音し、学習して再生してみたので記事にしたいと思う。

やったこと

・母音の録音
・録音された母音を通常のMNISTと同様なモデルで学習・再生してみる
・同じ録音をLSTMモデルで学習・再生してみる

・母音の録音

これは参考のアプリで実施した。
【参考】
AudioAutoencoder/save_boin_fig.py
構造は簡単で、工夫点は以下のとおり、サンプリング周波数を64x64x4としています。また、測定時間をsec=0.25秒としています。
この程度で無いと、短すぎて音をキャッチできません。
また、サンプリング周波数は、0.25秒で64x64=4096となるように決めています。
あまり、遅すぎても信号の構造がつぶれるだろうという配慮です。

fr=16384 #44100
sec=0.25

今回の主題としては、音声データをWavファイルでなく、Textファイルに保存してDLの入力データとして信号データとして使えるようにした部分がオリジナルです。

ファイルへの出力とファイルからの入力は以下の関数で実施します。
※以下の3点はうまく動くように当初のものから変更しています
encoding="utf-8" ;追記
sin_wav = (float(x) for x in row) ;float(x)として抽出
sin_wave = [int(float(x)* 32767.0 ) for x in sin_wav] ;int(float(x)*32767.0) 

# listをCSVファイルで出力
def writecsv(path,sk,output_data):
    with open(path+str(sk)+'.txt', 'w', newline='\n', encoding="utf-8") as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(output_data)

def readcsv(path,sk):
    with open(path+str(sk)+'.txt', newline='') as csvfile:
        spamreader = csv.reader(csvfile, delimiter=',')
        sin_wav=[]
        for row in spamreader:
            sin_wav = (float(x) for x in row)
    sin_wave = [int(float(x)* 32767.0 ) for x in sin_wav]    
    binwave = struct.pack("h" * len(sin_wave), *sin_wave)        
    return binwave,sin_wave     

・録音された母音を通常のMNISTと同様なモデルで学習・再生してみる

これでできそうだというのは、母音の信号を二次元に配列すると当然それぞれの特徴を表す画像になります。
信号の位相を変更して画像にしても、母音ごとの特徴は引き継いだものとなるだろうという仮説です。
そして、あわよくばノイズもあのMNISTのdenoisingと同様に消えてくれるといいなということで実施しました。
また、一度うまく学習できるようであれば、音質変換もカラーリングと同じような手法で実施できるという思惑があります。
実際のコードは以下のようなものです。
AudioAutoencoder/AE4boin_dense.py
モデルの拡張性を持たせるために、以下のように関数にしています。
ここで潜在空間は大きい方がAutoencoderの性能はいいわけですが、ここでは以下のようなパラメータを実施してみました。
※64以上であればほぼ同様に音声を再生できました

encoding_dim =1024 #256 #64  #32
input_img = Input(shape=(64*64,))
def encoder(input_img):
    encoded = Dense(encoding_dim, activation='relu')(input_img)
    return encoded

def decoder(encoded):
    decoded = Dense(64*64, activation='sigmoid')(encoded)
    return decoded

encoded=encoder(input_img)
decoded=decoder(encoded)
autoencoder = Model(input_img, decoded)

以下のような二次元モデルも実施してみました。

input_img = Input(shape=(64,64, 1)) 
def encoder(input_img):
    x = Conv2D(64, (3, 3), activation='relu', padding='same')(input_img)
    x = MaxPooling2D((2, 2), padding='same')(x)
    x = Conv2D(32, (3, 3), activation='relu', padding='same')(x)
    x = MaxPooling2D((2, 2), padding='same')(x)
    x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)
    encoded = MaxPooling2D((2, 2), padding='same')(x)
    return encoded

def decoder(encoded):
    x = Conv2DTranspose(16, (3, 3), activation='relu', strides=2, padding='same')(encoded)
    x = Conv2DTranspose(32, (3, 3), activation='relu', strides=2, padding='same')(x)
    x = Conv2DTranspose(64, (3, 3), activation='relu', strides=2, padding='same')(x)
    decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)
    return decoded

コード全体は以下におきました。
AudioAutoencoder/AE4boin.py
※注意)このコードは学習部分をコメントアウトして予測になっています
このモデルの場合の方がDenseモデルよりもパラメータ数は抑えられます。
しかし、実際上記と再現される音を比較するとどうもDenseモデルの方が聞き取りやすい音を再生できました。
得られた二次元画像を以下に貼っておきます。
training_64_dense1024.jpg
奇数行がオリジナルで偶数行が再生画像です。
黒さは異なりますが、かなり一致した画像が再生できており、MNISTと同様なレベルではできていると思います。
当初、雑音の中に一応「あ”~、え”~、い”~、お”~、う”~」と聞こえたときはそれなり感動しました。
しかし、思惑とは異なりノイズが乗っていて、聞き取りずらい状況です。特に「あ」はほぼ雑音で、上記のようにモデルを変更したり、その他試してみましたがあまり改善しませんでした。
※もっと深いCNNを利用することも考えましたが、ここは素直にLSTMで実施することとしました

・同じ録音をLSTMモデルで学習・再生してみる

コードは参考のとおり、KerasTeamのExampleからの受け売りです。
【参考】
keras/examples/lstm_stateful.py
このコードを少し改変して乱数波ではなく、音声に対応させると以下のとおりになります。
AudioAutoencoder/lstm_stateful_aiueo.py

LSTMコード解説

以下がKerasTeamのオリジナルExampleの関数です。乱数発生してデータ生成しています。

def gen_uniform_amp(amp=1, xn=10000):
    data_input = np.random.uniform(-1 * amp, +1 * amp, xn)
    data_input = pd.DataFrame(data_input)
    return data_input

これをまずは、以下のように複数サイン波の和に変更します。

def gen_uniform_amp(amp=1, xn=10000):
    x0=0
    step=1
    period=200
    k=0.0001
    cos = np.zeros(((xn - x0) * step))
    for i in range(len(cos)):
        idx = x0 + i * step
        cos[i] = amp * (np.cos(20 * np.pi * idx / period))+amp * (np.cos(40 * np.pi * idx / period))
        cos[i] = cos[i]* np.exp(-k * idx)
    data_input = pd.DataFrame(cos)    
    return data_input

次に、以下のようなフォルマント合成に変更してより音声に近づけます。

def sin_wav(A,f0,fs,t):
    point = np.arange(0,fs*t)
    sin_wav =A* np.sin(2*np.pi*f0*point/fs)
    return sin_wav
def create_sin_wav(A,f0,fs,t):
    sin_wave=0
    print(A[0])
    #int_f0=int(f0[0])
    for i in range(0,len(A),1):
        f1=f0[i]
        sw=sin_wav(A[i],f1,fs,t)
        sin_wave += sw
    sin_wave = [x * 1.0 for x in sin_wave]  #32767.0
    return sin_wave
def gen_uniform_amp(amp=1, xn=10000):
    x0=0
    step=1
    period=200
    k=0.0001
    #cos = np.zeros(((xn - x0) * step))
    A=(0.07,0.09,0.08,0.19,0.08,0.07) #a
    f0=261
    f=(f0,2*f0,3*f0,4*f0,5*f0,6*f0) #a
    fs=44100
    t=0.25 #sec
    sin_wav=create_sin_wav(A,f,fs,t)
    return sin_wav

上記の関数を用いて、以下のようにsin_wave(信号)と音声用のbinwaveを作成します。
そして、output =stream.write(binwave)で音として聞くことができます。

to_drop = max(tsteps - 1, lahead - 1)
sin_wave = gen_uniform_amp(amp=0.1, xn=input_len + to_drop)
print(sin_wave)
data_input = pd.DataFrame(sin_wave)
sin_wave = [int(float(x)* 32767.0 ) for x in sin_wave] 
binwave = struct.pack("h" * len(sin_wave), *sin_wave)
output =stream.write(binwave)

一方、sin_waveは以下のような出力(フォルマント合成)が得られます。
この時点で以下のような綺麗な波形を学習できました。
Stateful_a_32767.jpg
※この時点では縦軸が大きな値になっています
これを録音ファイル読込に変更します。

def readcsv_(path,sk):
    with open(path+str(sk)+'.txt', newline='') as csvfile:
        spamreader = csv.reader(csvfile, delimiter=',')
        sin_wav=[]
        for row in spamreader:
            #print("row",row)
            sin_wav = (float(x) for x in row)

    sin_wave = [int(float(x)* 32767)  for x in sin_wav]   
    binwave = struct.pack("h" * len(sin_wave), *sin_wave)        
    return binwave,sin_wave

to_drop = max(tsteps - 1, lahead - 1)
sin_wave=[]
list_=('a','e','i','o','u')
for sky in range(0,1,1):
    sky=list_[sky]
    for sk in range(0,1,1):
        b,x_ = readcsv_("./aiueo/sig_0730/"+sky+"_64x64/boin_fig",sk)
        sin_wave += x_
for i in range(len(sin_wave)):
    sin_wave[i]=sin_wave[i]/32762.
sky='_all'
data_input = pd.DataFrame(sin_wave)

ここで、音声ファイルをskyという値でlist_に定義した'a'などを選んでファイルを呼び出しています。また、今回は'a'の最初のファイルだけ使って学習再生しているのが分かります。
このコードで以下のような図が得られました。
隣接点近似した図が以下のように得られます。
inputExpected_a.jpg
そして、元信号と再生信号は以下のように出力されました。
Stateful_a.jpg
また、再生「あ」も綺麗に聞けました。
実は、上記は学習データを4096個の時系列データに対して80%を学習、20%を検証データにしているので、以下のように学習・検証しています。
Train on 3276 samples, validate on 819 samples
これは、自由に変更できて、例えば800データで学習しても同じように学習・再生されました。
一応、「あ、え、い、お、う」に対してそれぞれ学習したものを貼り合わせると、以下のような画像が得られました。
※なお、上記もですが、タイトルが異なっていて、オリジナルは予測値は差分でしたが、これはそれぞれの予測値そのものを示しています
lstm_aeiou.gif

一方、3個ずつ読み込んで同時に学習するために、データ作成を以下のように変更しました。
※すなわち、素で3等分して、学習に1個、検証に1個そして、最後の予測に1個使ってみることとしました

# split train/test data
def split_data(x, y, ratio=0.33): #0.8
    to_train = int(input_len * ratio)
    # tweak to match with batch_size
    #to_train -= to_train % batch_size
    to_test = int(input_len * ratio*2)

    x_train = x[:to_train]
    y_train = y[:to_train]
    x_test = x[to_train:to_test]
    y_test = y[to_train:to_test]
    x_val =  x[to_test:]
    y_val =  y[to_test:]
    # tweak to match with batch_size
    #to_drop = x.shape[0] % batch_size
    """
    if to_drop > 0:
        x_test = x_test[:-1 * to_drop]
        y_test = y_test[:-1 * to_drop]
    """
    # some reshaping
    reshape_3 = lambda x: x.values.reshape((x.shape[0], x.shape[1], 1))
    x_train = reshape_3(x_train)
    x_test = reshape_3(x_test)
    x_val = reshape_3(x_val)
    reshape_2 = lambda x: x.values.reshape((x.shape[0], 1))
    y_train = reshape_2(y_train)
    y_test = reshape_2(y_test)
    y_val = reshape_2(y_val)
    return (x_train, y_train), (x_test, y_test), (x_val, y_val)

この学習だと以下のように若干、元画像と生成画像にずれがでましたが、再生上はそれぞれ綺麗に再生できました。
Stateful_all_05.jpg
なお、データ読み込みを以下のように変更しています。

input_len = 4096*15
to_drop = max(tsteps - 1, lahead - 1)
sin_wave=[]
list_=('a','e','i','o','u')
for sk in range(0,3,1):
    for sky in range(0,5,1):
        sky=list_[sky]
        b,x_ = readcsv_("./aiueo/sig_0730/"+sky+"_64x64/boin_fig",sk)
        sin_wave += x_
for i in range(len(sin_wave)):
    sin_wave[i]=sin_wave[i]/32762.
sky='_all_05'
print(sin_wave)        
data_input = pd.DataFrame(sin_wave)

まとめ

・母音を録音してAutoencoderとLSTMで学習・再生させてみた
・Autoencoderではノイズが乗りちょっと聞きずらいが、LSTMは少ないデータで綺麗に再生できることが分かった

・基本的な音声を学習させ、複雑な音声について再生できるか確認したい
・声質変換(男性⇒女性など)を実施する

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6