なんもわからんな

条件付きC RNN GANで音楽を生成

前置き

昨年のGANの動向を見ると,半教師あり学習・条件付きでの生成がトレンドだったように思います. そこでC-RNN-GANのモデルを元にを条件付きで音楽を生成したいと思います.

条件付きGAN

条件付きのGANについてはこちらの論文を参照します.
モデル図は下記の通りのようになっています.
f:id:yakuta55:20180205102708p:plain 生成器には,ランダムな分布zとラベルyを元に生成しています. 一方識別器は,生成されてもの/学習データxとラベルyを入力しxであるかの真偽を出力しています.

最近ではこれとCycleGANなどを組み合わせたStarGANなどがあります.

C-RNN-GAN

C-RNN-GANは以下のようなモデル図になっています. f:id:yakuta55:20180205120421p:plain
基本的に生成器も識別器もRNNになった感じです.
ただ識別器側はBiRNNになっています.
また損失関数も通常のGANと変わりはありません.

Conditional C-RNN-GAN

モデル形状はC RNN GANで同じで,両方のモデルにラベルを加えて入力しています. 損失はWGANを使用しました.

生成結果はこちらにおいておきます.

Conditional_C_RNN_GAN/generated_mid at wgan · TrsNium/Conditional_C_RNN_GAN · GitHub

聞いてみればわかるのですが,mode collapseがおきています.
これを回避するためにWGANなどを入れて見たのですが上手く学習をすることができなかったようです.

コードは以下に置いておきます.

データセット

freemidiからデータを集めました.
一応カテゴライズされているので,そのカテゴリーに沿ってラベルを作成しデータセットを作成しました.
スクレイピングのコードは下記にあります.
実行するときは,chrome driverが必要なのでご用意してください.

from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.request import urlretrieve
import re
import os
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time

content_url = "https://freemidi.org/"
html_doc = urlopen(content_url+"genre").read()
sp = BeautifulSoup(html_doc)

genres = sp.find_all("div", {"class":"genre-big-ones"})
genres_href = [[tag["href"] for tag in genre.find_all("a")] for genre in genres]

'''
[['genre-rock', 'genre-pop', 'genre-hip-hop-rap', 'genre-rnb-soul'],
 ['genre-classical', 'genre-country', 'genre-jazz', 'genre-blues'],
 ['genre-dance-eletric', 'genre-folk', 'genre-punk', 'genre-newage'],
 ['genre-reggae-ska', 'genre-metal', 'genre-disco', 'genre-bluegrass']]
'''

genre_artist = {}
#お好きなジャンルをお選びください
selected_genre = []
for hrefs in genres_href:
    for href in hrefs:
        if not href in selected_genre:
            continue
        content = {}
        url = content_url + href
        html_doc = BeautifulSoup(urlopen(url).read(), "lxml")
        artist_hrefs = html_doc.find_all("div", {"class": "genre-link-text"})
        print(href)
        for artist_href in artist_hrefs:
            artist_href_ = artist_href.a.get("href")
            a_html_doc = BeautifulSoup(urlopen(content_url+artist_href_).read())
            content[artist_href.a.string] = {re.sub("\r\n\s{2,}", "", artist_href.a.string):a.get("href") for a in a_html_doc.find_all("a", {"itemprop":"url"})[1:]}
        genre_artist[href] = content

cwd = os.getcwd()
for key, item in genre_artist.items():
    if not os.path.exists(key):
        os.mkdir(key)
        
    chromeOptions = webdriver.ChromeOptions()
    prefs = {"download.default_directory" : cwd+"/"+key+"/"}
    chromeOptions.add_experimental_option("prefs",prefs)
    
    for artist_n, song_dict in item.items():                           
        for song_n, song_href in song_dict.items():
            try:
                browser = webdriver.Chrome(executable_path="chromedriver", chrome_options=chromeOptions)
                browser.get(content_url+song_href)
                browser.find_element_by_link_text('Download MIDI').click()
                time.sleep(5)
                browser.quit()
            except:
                continue

おわり

想定していた結果が得れませんでしたが,考え直すとデータ数が少なかったのかもしれません. データ数を増やしたり損失関数を工夫してもう一度挑戦して行きたいと思います.

Shake Shakeの実装と簡単な解説

前書き

最近画像系コンペに再び参加してみようと思い,既存の分類精度が高いモデルをもう一度調べたいと思ったので,メモ程度に残したいと思います.

Shake Shakeとは

Shake ShakeとはResNetの一種で,中間層でdata augmentationをしており正則化をしています. shake shakeのモデル図は,以下のように2つに分岐しており分岐しています.
foward時はそれぞれの最後でαi ∈ [0, 1] を乗算しbackward時はαiとは異なる βi ∈ [0, 1] を使用します.またtest時は0.5で固定してやるそうです. forward時のこれは,画像に含まれる物体の割合が変化してもロバストに識別ができるように学習ができるようです.
backward時は,勾配にノイズを加えると精度が向上するためであり,αiと違う乱数を用いいることでさらに強い正則化効果を持たすことができるようです. f:id:yakuta55:20180131100207p:plain

また論文にはThe skip connections represent the identity function except during downsampling where a slightly customized structure consisting of 2 concatenated flows is used. Each of the 2 flows has the following components: 1x1 average pooling with step 2 followed by a 1x1 convolution. The input of one of the two flows is shifted by 1 pixel right and 1 pixel down to make the average pooling sample from a different position. The concatenation of the two flows doubles the width. のような記述があり,skipをダウンサンプリングする場合にはskipをの1つは右に1下に1ずらす必要がありそうです.その後に1x1average pooligと2つの畳み込みをした後にもう1つも同様にpoolingと畳み込みをしたものと結合するようです.

実装

実装はTensorflowで書きました.
Residual Blockの部分を抜粋して載せておきます.

import tensorflow as tf

def residual_block(x, a, filter_size, stride):
    def convolution(x, l):
        h = tf.nn.relu(x)
        h = tf.layers.conv2d(h, filter_size, [3,3], [stride, stride], padding="SAME")
        h = tf.nn.relu(tf.layers.batch_normalization(h))
        h = tf.layers.conv2d(h, filter_size, [3,3], padding="SAME")
        h = tf.layers.batch_normalization(h)
        return h  * (a if l else 1-a)
    
    def down_sampling(x):
        x = tf.nn.relu(x)
        h1 = tf.layers.average_pooling2d(x, [1,1], [2,2])
        h1 = tf.layers.conv2d(h1, filter_size/2, [1,1], padding="SAME")
        h2 = tf.pad(x[:, 1:, 1:] ,[[0,0], [0,1], [0,1], [0,0]])
        h2 = tf.layers.average_pooling2d(h2, [1,1], [2,2])
        h2 = tf.layers.conv2d(h2, filter_size/2, [1,1], padding="SAME")
        return tf.concat([h1, h2], axis=-1)

    lbranch = convolution(x, True)
    rbranch = convolution(x, False)
    branch = lbranch + rbranch

    if not x.get_shape().as_list()[-1] == filter_size:
        x = down_sampling(x)
    else:
        x = tf.identity(x, name='x')

    return x + branch 

参考文献

Shake-Shake regularization

[サーベイ論文] 畳み込みニューラルネットワークの研究動向
公式の実装