条件付きC RNN GANで音楽を生成
前置き
昨年のGANの動向を見ると,半教師あり学習・条件付きでの生成がトレンドだったように思います. そこでC-RNN-GANのモデルを元にを条件付きで音楽を生成したいと思います.
条件付きGAN
条件付きのGANについてはこちらの論文を参照します.
モデル図は下記の通りのようになっています.
生成器には,ランダムな分布zとラベルyを元に生成しています.
一方識別器は,生成されてもの/学習データxとラベルyを入力しxであるかの真偽を出力しています.
最近ではこれとCycleGANなどを組み合わせたStarGANなどがあります.
C-RNN-GAN
C-RNN-GANは以下のようなモデル図になっています.
基本的に生成器も識別器も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と違う乱数を用いいることでさらに強い正則化効果を持たすことができるようです.
また論文には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