pythonに作曲してもらう 第2弾
カテゴリ:Pythonの話
こんにちは。inglowの開発担当です。
今回は、近年超キテる「python」を使った作曲の第2弾です。
今回は、「Keras」を利用してメロディを学習させてみました。
第1弾の記事はこちらです。
▼pythonに作曲してもらう
https://inglow.jp/techblog/python-scoremake/
開発環境は下記です。
- バージョン:python3.8.3
- ライブラリ:music21、keras
- エディタ:notepad++、MuseScore3(musicXMLの表示用)
また、弊社ではWebプロモーション成功事例集をまとめた限定資料を無料で配布しています。
Webマーケティングに興味がある方は、下記ページより目を通してみてください。
またwebで集客する方法を別の記事にまとめております。
詳しく解説しているので、web集客について深く知りたい方は、ぜひこちらもご覧ください。
今回作るもの
前回は、「マルコフ連鎖」というアルゴリズムを使用して作曲をしてみましたが、今回は「LSTM」を使って作曲をしてみます。
学習の方法としては、前回と同じように、読み込んだmusicXMLの楽譜データをテキストにし、音符一つを1単語として学習をさせます。
学習させるプログラムの準備
musicXMLからテキストに変換する処理は前回から持ってきて、学習させる部分を変更します。
ソースコードは、kerasのサンプルコードを少し変更して利用しました。
Keras 文章生成 サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
from __future__ import print_function from keras.callbacks import LambdaCallback from keras.models import Sequential from keras.models import load_model from keras.layers import Dense from keras.layers import LSTM from keras.optimizers import RMSprop import numpy as np import random import sys import io from tqdm import tqdm import music21 as m21 import os import glob DS = os.sep bs = os.path.dirname(__file__) + DS xmlpath = bs + 'musicxml_simple' + DS model_weights_path = bs + 'melo_model' + 'w.hdf5' model_save_path = bs + 'melo_model' + '.hdf5' make_model = False #music_keys = ('C', 'D', 'E', 'F', 'F#', 'G', 'A', 'B') music_keys = ('C') #テキストの生成 text = [] #フォルダ内のxmlファイルを取得する xmls = glob.glob(xmlpath + "*.musicxml") for x in tqdm(xmls): #xmlを読み込む piece = m21.converter.parse(x) for trans_key in music_keys: k = piece.analyze('key') #主音を合わせる trans = trans_key i = m21.interval.Interval(k.tonic, m21.pitch.Pitch(trans)) trans_piece = piece.transpose(i) ntxt = [] for n in trans_piece.flat.notesAndRests: text.append(str(n.name) + '_' + str(n.duration.quarterLength) + ' ') #ここからLSTM print('--------- start LSTM') chars = text count = 0 char_indices = {} #辞書 indices_char = {} #逆引き辞書 for word in chars: if not word in char_indices: char_indices[word] = count count +=1 print(count, word) #登録した単語を表示 #逆引き辞書を辞書から作成する indices_char = dict([(value, key) for (key, value) in char_indices.items()]) maxlen = 5 step = 1 sentences = [] next_chars = [] for i in range(0, len(text) - maxlen, step): sentences.append(text[i: i + maxlen]) next_chars.append(text[i + maxlen]) print('nb sequences:', len(sentences)) #モデルのファイルがある場合は読み取る if(os.path.exists(model_save_path) and os.path.exists(model_weights_path)): print('-----------read Model') model=load_model(model_save_path) model.load_weights(model_weights_path) else: #モデル生成準備 make_model = True print('Vectorization...') 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 #モデル生成 print('Build model...') model = Sequential() model.add(LSTM(128, input_shape=(maxlen, len(chars)))) model.add(Dense(len(chars), 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) def on_epoch_end(epoch, _): print() print('------- Generating text after Epoch: %d' % epoch) start_index = random.randint(0, len(text) - maxlen - 1) start_index = 0 #テキストの最初からスタート for diversity in [0.2]: #ここは0.2のみ? print('--------diversity:', diversity) generated = '' sentence = text[start_index: start_index + maxlen] #sentenceはリストなので文字列へ変換 generated += ''.join(sentence) print(sentence) print('--------- Generating with seed:"' + "".join(sentence) + '"') sys.stdout.write(generated) for i in range(100): x_pred = np.zeros((1, maxlen, len(chars))) for t, char in enumerate(sentence): x_pred[0, t, char_indices[char]] = 1. preds = model.predict(x_pred, verbose=0)[0] next_index = sample(preds, diversity) next_char = indices_char[next_index] generated += next_char sentence = sentence[1:] sentence.append(next_char) sys.stdout.write(next_char) sys.stdout.flush() print() def on_train_end(logs): print('----- saving model...') model.save_weights(model_weights_path) model.save(model_save_path) def make_melody(length=200): start_index = random.randint(0, len(text) - maxlen - 1) #start_index = 0 #テキストの最初からスタート print(start_index) for diversity in [0.2]: #ここは0.2のみ? print('--------diversity:', diversity) generated = '' sentence = text[start_index: start_index + maxlen] #sentenceはリストなので文字列へ変換 generated += ''.join(sentence) print(sentence) print('--------- Generating with seed:"' + "".join(sentence) + '"') sys.stdout.write(generated) for i in range(length): x_pred = np.zeros((1, maxlen, len(chars))) for t, char in enumerate(sentence): x_pred[0, t, char_indices[char]] = 1. preds = model.predict(x_pred, verbose=0)[0] next_index = sample(preds, diversity) next_char = indices_char[next_index] generated += next_char sentence = sentence[1:] sentence.append(next_char) sys.stdout.write(next_char) sys.stdout.flush() print() return generated if make_model: print_callbak = LambdaCallback(on_epoch_end=on_epoch_end, on_train_end=on_train_end) model.fit(x, y, batch_size=128, epochs=120, callbacks=[print_callbak]) print('-------print score') melo_sentence = make_melody(20) print(melo_sentence) #メロディをmusicXMLに変換する meas = m21.stream.Stream() meas.append(m21.meter.TimeSignature('4/4')) melo = melo_sentence.split() for m in melo: ptch, dist = m.split('_') if(ptch == 'rest'): n = m21.note.Rest(quarterLength = float(dist)) else: n = m21.note.Note(ptch,quarterLength = float(dist)) meas.append(n) #print(note_dt) meas.makeMeasures(inPlace=True) meas.show('musicxml', addEndTimes=True) |
学習させる
実装したプログラムを動かしてみます。
パラメーターや学習データ量、PCのスペックにによっては、丸1日かかったりすることもあるみたいです。
今回使用した学習データは、サビ部分のみの楽譜データを15個なので、だいたい10分~30分ほどで終わります。
もともとのソースをそのまま実行すると、動画のように、epochごとに学習した内容から文字列を生成しています。最初は同じ音符ばかりが出力されているみたいですが、だんだんいろんな音符が出てきます。
ひとまず、サンプルコードの状態で生成をしてみました。感がいい人にはわかるぐらい曲の原型がまだ残っています…。
また、同じメロディの繰り返しにもなってしまっています。
パラメーターを変更して試す
パラメーターをいろいろいじってみようと思いますが…。人工知能の開発についてはまだまだ勉強中なため、最初はどこをさわったらいいのか…と思っていました。
今回は、下記のパラメーターを変更してどのように変化があるか実験してみます。
- model.add()で追加している、「LSTM()」レイヤーの第一引数
- model.fit()で指定している「epochs」の数値
LSTM = 128 epochs = 120
epochsを2倍にしてみました。まだ原型は残っていますが、残り方が少しマイルドになってきました。
LSTM = 500 epochs = 120
LSTMの値をめっちゃ高く設定しました。
ちょっとぽくなって来ています。原型もあんまりわからなくなってきました。
LSTM = 500 epochs = 300/h3>
epochsをさらに大きくしてみました。
ここまでくると大分オリジナリティのあるメロディが生成されてきました。
さいごに
せっかくなので、一番気に入ったメロディに伴奏をつけてみました。
マルコフ連鎖とはまたちがった感じでメロディが生成されましたが、ド→シの動きが好きなのは学習したデータの影響なのでしょうか…。たびたび出現します。
私が最初に音楽の自動生成を体験したのは、2019年3月に公開されていた「Google Doodle」のロゴでした。
すげーーーー!!と感動したのですが、簡易的とはいえ、まさか自分でも作曲させるAIが作れる時がくるとは…と思っています。
音楽は理論がちゃんとあり、数学的な側面もあるにもかかわらず、感情に訴えかけることができるのが魅力だと思います。
今後さらに音楽×プログラミングの研究の分野に注目していきたいと思います。
弊社inglowでは、これから広告の運用を考えられている方、あるいはこれから広告代理店に運用をお願いされる方向けに、「業界別Web広告の成功事例」をまとめた資料を無料配布しております。
下記のフォームに入力いただくだけで、無料で資料をダウンロードしていただけます。ぜひご利用下さい。