Help us understand the problem. What is going on with this article?

RADWIMPSっぽい曲LSTMで自動生成してみた(1)(歌詞と、コード進行のみ)

More than 1 year has passed since last update.

はじめに

「週1モノ作りチャンジ」として今回は、RADWIMPSっぽい曲をLSTMで自動生成してみました。
憧れのアーティストの歌詞とコード進行の特徴を分析して自動生成することが出来れば、これで自分もトップアーティストになれるぜ、やったぜ!!いえええい!と思いやりました。

Qiitaの年齢層的には、「さだまさし」「椎名林檎」「ミスチル」あたりにした方がウケるんだろうなあ、とも思っていたのですが、あんま知らないしなあと思いやめました。(Unison Square Garden が1番好き。)

学習に使ったのは、LSTM(TensorFlow)と、あとマルコフ連鎖も歌詞のところで試しに使いました。
学習データには、10曲分の歌詞とそのコード進行を使用しました。
U-フレットLyricWikiを使用)

歌詞編

とりあえず、LyricsWikiから歌詞を適当に10曲分持ってきて、テキストファイルに保存します。
マルコフ連鎖の時「。」で区切るので、曲の終わりに「。」を付けておきます。

LSTM

とりあえず(まあ、どうせ上手くいかないんだろうなあと思いつつも)LSTMで学習させてみます。
学習は120回,生成文字数は100にしておきました。
(GPU使ってめちゃめちゃ学習させたり、他のパラメータいじればもっと向上する余地はあると思います。)

コード

lyrics_lstm.py
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM
from keras.optimizers import RMSprop
from keras.utils.data_utils import get_file
import numpy as np
import random
import sys

path = './rad_lyrics.txt'
text = open(path, "r").read()

chars = sorted(list(set(text)))
char_indices = dict((c,i) for i,c in enumerate(chars))
indices_char = dict((i,c) for i,c in enumerate(chars))

maxlen = 40
step = 3
sentences = []
next_chars = []
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])

# テキストのベクトル化
X = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)

for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        X[i, t, char_indices[char]] = 1
    y[i, char_indices[next_chars[i]]] = 1

# モデルを定義する
model = Sequential()
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dense(len(chars)))
model.add(Activation('softmax'))
optimizer = RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

def sample(preds, temperature=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

for iteration in range(1,120):
    print()
    print('-' *50)
    print('繰り返し回数: ', iteration)
    model.fit(X, y, batch_size=128, epochs=1)

    start_index = random.randint(0, len(text)-maxlen-1)

    for diversity in [0.2, 0.5, 1.0, 1.2]:
        print()
        print('-----diveristy', diversity)

        generated = ''
        sentence = text[start_index: start_index + maxlen]
        generated += sentence
        print('----- Seedを生成しました: "' + sentence + '"')
        sys.stdout.write(generated)

        for i in range(100):
            x = np.zeros((1,maxlen,len(chars)))
            for t, char in enumerate(sentence):
                x[0, t, char_indices[char]] = 1.

            preds = model.predict(x, verbose=0)[0]
            next_index = sample(preds, diversity)
            next_char = indices_char[next_index]

            generated += next_char
            sentence = sentence[1:] + next_char

            sys.stdout.write(next_char)
            sys.stdout.flush()
        print()

結果

↓18回目
lstm_1.png

↓38回目
lstm2.png

↓123回目
lstm3.png
  
   
   

"いいんですか"が止まらねえええ
めっちゃ"いいんですか"が横から入ってくる。強すぎます。
学習データに「セプテンバーさん」を入れた時点で負けだったのかもしれないです。

それか、自分はまだいまいちLSTMの書き方をわかってないので単に勉強不足説もあります。
何か気づいたら是非教えてください。m(._.)m

マルコフ連鎖

LSTMがまあ悲惨な結果だったので、マルコフ連鎖をします。
自分は以前落合陽一っぽいツイート自動生成してツイートさせてみたでお世話になってるので、こっちはもうお手の物です!期待大!

コード

”。”までを1回分とし、1回分を生成させます。

markov.py
from janome.tokenizer import Tokenizer
import json

# テキストファイルを読み込む
sjis = open('rad_lyrics.txt', 'rb').read()
text = sjis.decode('utf_8')

# テキストを形態素解析読み込みます
t = Tokenizer()
words = t.tokenize(text)

# 辞書を生成
def make_dic(words):
    tmp = ["@"]
    dic = {}
    for i in words:
        word = i.surface
        if word == "" or word == "\r\n" or word == "\n": continue
        tmp.append(word)
        if len(tmp) < 3: continue
        if len(tmp) > 3: tmp = tmp[1:]
        set_word3(dic, tmp)
        if word == "。":
            tmp = ["@"]
            continue
    return dic

# 三要素のリストを辞書として登録
def set_word3(dic, s3):
    w1, w2, w3 = s3
    if not w1 in dic: dic[w1] = {}
    if not w2 in dic[w1]: dic[w1][w2] = {}
    if not w3 in dic[w1][w2]: dic[w1][w2][w3] = 0
    dic[w1][w2][w3] += 1

dic = make_dic(words)
json.dump(dic, open("markov-blog.json", "w", encoding="utf-8"))

##自動生成
import json
dic = open("markov-blog.json" , "r")
dic = json.load(dic)

tweets_list = []
import random
def word_choice(sel):
    keys = sel.keys()
    ran = random.choice(list(keys))
    return ran

def make_sentence(dic):
    ret = []
    if not "@" in dic: return "no dic"
    top = dic["@"]
    w1 = word_choice(top)
    w2 = word_choice(top[w1])
    ret.append(w1)
    ret.append(w2)
    while True:
        w3 = word_choice(dic[w1][w2])
        ret.append(w3)
        if w3 == "。": break
        w1, w2 = w2, w3
    tweets_list.append(ret)
    return "".join(ret)

for i in range(1):
    s = make_sentence(dic)
    tweets_list.append(s)
    print(s)

結果

↓曲っぽい
markov.png

↓満点の空はきっとセプテンバー(最後ちょっと怖い)
markov1.png

↓迷わずYOU!!バージョン
markov2.png

↓満点の空に君のクローンが
markov3.png
  
  

良い感じですね!
さすがマルコフ連鎖。

コード進行編

次に、コード進行も自動生成していきます。
コード進行はLSTMだけで実装しました。
似たことやってる人いないかなあと探してみると

RNNを用いたコード進行自動生成
ディープラーニングによるコード進行の予測

など既に上がっているものがあるのですが、中身のコードは載っていないため自分レベルだと完全に再現することはできませんでした。(く、くやしいい。)
大人しく、歌詞の時に使ったLSTMをそのまま使います。

コード

入力数は1にしときます。

code_lstm.py
from keras.layers import Dense, Activation, LSTM
from keras.optimizers import RMSprop
from keras.utils.data_utils import get_file
import numpy as np
import random
import sys

chars = sorted(list(set(text)))
print('Total chars:', len(chars))

char_indices = dict((c,i) for i,c in enumerate(chars))
indices_char = dict((i,c) for i,c in enumerate(chars))

maxlen = 1
step = 3
sentences = []
next_chars = []
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])

# テキストのベクトル化
X = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)

for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        X[i, t, char_indices[char]] = 1
    y[i, char_indices[next_chars[i]]] = 1


# モデルを定義する
model = Sequential()
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dense(len(chars)))
model.add(Activation('softmax'))
optimizer = RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

def sample(preds, temperature=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

for iteration in range(1,10):
    print()
    print('-' *50)
    print('繰り返し回数: ', iteration)
    model.fit(X, y, batch_size=128, epochs=1)

    start_index = random.randint(0, len(text)-maxlen-1)

    for diversity in [0.2, 0.5, 1.0, 1.2]:
        print()
        print('-----diveristy', diversity)

        generated = ''
        sentence = text[start_index: start_index + maxlen]
        generated += sentence
        print('----- Seedを生成しました: "' + sentence + '"')
        sys.stdout.write(generated)

        for i in range(40):
            x = np.zeros((1,maxlen,len(chars)))
            for t, char in enumerate(sentence):
                x[0, t, char_indices[char]] = 1.

            preds = model.predict(x, verbose=0)[0]
            next_index = sample(preds, diversity)
            next_char = indices_char[next_index]

            generated += next_char
            sentence = sentence[1:] + next_char

            sys.stdout.write(next_char)
            sys.stdout.flush()
        print()

結果

Cから始まるパターン

C → D → A → C → C → D → A → D → D → A → C → A → D
C → G → G → Bm → C → A → C → A → D → D → A → D 
C → Bm7 → Bm → E → Bm → E → A → D → A → Bm7
C → G → A# → G → G → A → C# → C → F# → Em → E

Fから始まるパターン

F → G → A → D → Bm → G → G# → A → D → C → B → A
F → G → A → G → Em → G → G → A → Bm → E → A → G
F → C → G → Em → C → A → G → A → G → C → D → G

Gから始まるパターン

G → C → D → C → D → C → Em → C → G → F → C → Bm
G → A → G → D → E → Em → G → Bm7 → Em → G
G → C → A → G → D → D → G → G → A → C → A → E 

ここで重大なことに気づきました。
コード進行の知識がないから、これ見てもいい感じなのか全く分からない。。。

どうなんでしょう。。このコード進行はRADWIMPSっぽいんですかね?
ちょっと、わかる方いたら教えてくださいm(._.)m

おわりに

歌詞の方は、マルコフ連鎖で誤魔化せそうですが限界があるので、やはりLSTMで上手くいかない現状まだトップアーティストへの道は長そうです。
コード進行に関しては、そもそも良いのか悪いのかすら分からないのですが、あまり期待してないです。笑

今度は、野田洋次郎さんの声とか取り入れられたりしたらもっと面白くなるかなと思っています。
まだまだトップアーティストへの道は諦めてないのでこれからも頑張ります!
応援よろしくお願いします!

Twitterでは、エンジニア向けの面白そうな情報の共有や、「こんなアイデアあるんだけど作って欲しい!」といった依頼に無料で答えていますのでフォローの方してもらえると嬉しいです!(今の所まだアーティスト活動はしていません)

それでは!

ahpjop
週1でQiitaにupしてました。  Python, ML, DL, Tensorflow, Blockchain, Ruby on Rails, React, React Native, Vue, Solidity, SQL とか使って遊んでます。 気軽に連絡ください! Twitter -> 「@ahpjop」
https://seven0525.github.io/
weekend-engineer
あらゆるアイデアをカタチにするエンジニア集団
https://seven0525.github.io/we/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away