MORTM 0.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mortm-0.11.0/MORTM.egg-info/PKG-INFO +14 -0
- mortm-0.11.0/MORTM.egg-info/SOURCES.txt +16 -0
- mortm-0.11.0/MORTM.egg-info/dependency_links.txt +1 -0
- mortm-0.11.0/MORTM.egg-info/top_level.txt +1 -0
- mortm-0.11.0/PKG-INFO +14 -0
- mortm-0.11.0/README.md +1 -0
- mortm-0.11.0/mortm/ConvertMidi.py +277 -0
- mortm-0.11.0/mortm/PositionalEncoding.py +25 -0
- mortm-0.11.0/mortm/__init__.py +0 -0
- mortm-0.11.0/mortm/constants.py +19 -0
- mortm-0.11.0/mortm/datasets.py +98 -0
- mortm-0.11.0/mortm/gmail_messanger.py +66 -0
- mortm-0.11.0/mortm/messager.py +11 -0
- mortm-0.11.0/mortm/mortm.py +155 -0
- mortm-0.11.0/mortm/tokenizer.py +92 -0
- mortm-0.11.0/mortm/train.py +219 -0
- mortm-0.11.0/setup.cfg +4 -0
- mortm-0.11.0/setup.py +19 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: MORTM
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: 音楽の旋律生成を実現したシステム
|
|
5
|
+
Home-page: https://github.com/Ayato964
|
|
6
|
+
Author: Nagoshi Takaaki
|
|
7
|
+
Author-email: nagoshi@kthrlab.jp
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
pip install lxml
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
MORTM.egg-info/PKG-INFO
|
|
4
|
+
MORTM.egg-info/SOURCES.txt
|
|
5
|
+
MORTM.egg-info/dependency_links.txt
|
|
6
|
+
MORTM.egg-info/top_level.txt
|
|
7
|
+
mortm/ConvertMidi.py
|
|
8
|
+
mortm/PositionalEncoding.py
|
|
9
|
+
mortm/__init__.py
|
|
10
|
+
mortm/constants.py
|
|
11
|
+
mortm/datasets.py
|
|
12
|
+
mortm/gmail_messanger.py
|
|
13
|
+
mortm/messager.py
|
|
14
|
+
mortm/mortm.py
|
|
15
|
+
mortm/tokenizer.py
|
|
16
|
+
mortm/train.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mortm
|
mortm-0.11.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: MORTM
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: 音楽の旋律生成を実現したシステム
|
|
5
|
+
Home-page: https://github.com/Ayato964
|
|
6
|
+
Author: Nagoshi Takaaki
|
|
7
|
+
Author-email: nagoshi@kthrlab.jp
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
pip install lxml
|
mortm-0.11.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pip install lxml
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Midiデータを tokenizerで変換し、数値の配列にしたものをnumpyに変換し、指定されたディレクトリに保存します。
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import mido.midifiles.meta
|
|
8
|
+
from numpy import ndarray, dtype
|
|
9
|
+
from pretty_midi import PrettyMIDI, Instrument, Note
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from . import constants
|
|
13
|
+
from AGSM.convert import ConvTempo, ConvKey
|
|
14
|
+
from .tokenizer import Tokenizer
|
|
15
|
+
from . import tokenizer as tr
|
|
16
|
+
|
|
17
|
+
PITCH = 0
|
|
18
|
+
VELOCITY = 1
|
|
19
|
+
DURATION_INT = 2
|
|
20
|
+
DURATION_FEW = 3
|
|
21
|
+
BEGIN_TIME_INT = 4
|
|
22
|
+
BEGIN_TIME_FEW = 5
|
|
23
|
+
START_SEQ = np.array([[0, 0, 0, 0]], dtype=int)
|
|
24
|
+
END_SEQ = np.array([[5, 5, 5, 5]], dtype=int)
|
|
25
|
+
''''
|
|
26
|
+
規則:
|
|
27
|
+
AyaNode:
|
|
28
|
+
Aya_node = [音高, 強さ, 始まりの拍(16), 終わりの拍までの長さ(16)]
|
|
29
|
+
TOKEN:
|
|
30
|
+
<S_SEQ>: [0, 0, 0, 0]
|
|
31
|
+
<E_SEQ>: [5, 5, 5, 5]
|
|
32
|
+
'''
|
|
33
|
+
|
|
34
|
+
def convert_tolist(target: list):
|
|
35
|
+
new_target = None
|
|
36
|
+
for t in target:
|
|
37
|
+
t: ndarray
|
|
38
|
+
cul = []
|
|
39
|
+
for c in t:
|
|
40
|
+
cul.append(c)
|
|
41
|
+
if new_target is not None:
|
|
42
|
+
new_target = new_target + [cul]
|
|
43
|
+
else:
|
|
44
|
+
new_target = [cul]
|
|
45
|
+
|
|
46
|
+
return new_target
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConvertMidi:
|
|
50
|
+
|
|
51
|
+
def __init__(self, tokenizer: Tokenizer, directory: str, program_list: list, tempo: int, midi_data: PrettyMIDI = None):
|
|
52
|
+
self.directory = directory
|
|
53
|
+
self.midi_data = midi_data
|
|
54
|
+
self.program_list = program_list
|
|
55
|
+
self.tempo = tempo
|
|
56
|
+
self.aya_node = None
|
|
57
|
+
self.is_error = False
|
|
58
|
+
self.tokenizer = tokenizer
|
|
59
|
+
|
|
60
|
+
def convert(self):
|
|
61
|
+
self._ct_tempo()
|
|
62
|
+
#self._ct_key()
|
|
63
|
+
if not self.is_error:
|
|
64
|
+
convert_notes: np = None
|
|
65
|
+
program_count = 0
|
|
66
|
+
for inst in self.midi_data.instruments:
|
|
67
|
+
inst: Instrument = inst
|
|
68
|
+
if not inst.is_drum and inst.program in self.program_list:
|
|
69
|
+
cn = self.ct_aya_node(inst.notes)
|
|
70
|
+
|
|
71
|
+
if len(cn) >= 1000:
|
|
72
|
+
new_array = np.array_split(cn, np.arange(1000, len(cn), 1000))
|
|
73
|
+
new_array = convert_tolist(new_array)
|
|
74
|
+
cn: ndarray = np.array(self._padding(new_array))
|
|
75
|
+
print(cn.shape)
|
|
76
|
+
else:
|
|
77
|
+
cn: ndarray = np.array(self._padding([cn.tolist()], 1000))
|
|
78
|
+
|
|
79
|
+
if convert_notes is None:
|
|
80
|
+
convert_notes = cn
|
|
81
|
+
else:
|
|
82
|
+
#pad_convert_notes, pad_cn = self.padding(convert_notes, cn)
|
|
83
|
+
convert_notes = np.vstack((convert_notes, cn), dtype=int)
|
|
84
|
+
program_count += 1
|
|
85
|
+
|
|
86
|
+
if program_count == 0:
|
|
87
|
+
print(f"{self.directory}には欲しい楽曲がありませんでした")
|
|
88
|
+
self.is_error = True
|
|
89
|
+
elif np.min(convert_notes) < 0:
|
|
90
|
+
print(f"{self.directory}の値の中に0以下の値が格納されていたため、中断します。")
|
|
91
|
+
self.is_error = True
|
|
92
|
+
|
|
93
|
+
self.aya_node = convert_notes
|
|
94
|
+
return convert_notes
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def split_seq_data(self, target: ndarray):
|
|
98
|
+
|
|
99
|
+
if len(target.shape) == 1:
|
|
100
|
+
musics_seq = [target.tolist()]
|
|
101
|
+
else:
|
|
102
|
+
musics_seq = target.tolist()
|
|
103
|
+
|
|
104
|
+
new_music_seq = [[]]
|
|
105
|
+
for i in range(len(musics_seq)):
|
|
106
|
+
result = []
|
|
107
|
+
current_sublist = []
|
|
108
|
+
for value in musics_seq[i]:
|
|
109
|
+
current_sublist.append(value)
|
|
110
|
+
if value == 2:
|
|
111
|
+
result.append(current_sublist)
|
|
112
|
+
current_sublist = []
|
|
113
|
+
new_music_seq = new_music_seq + result
|
|
114
|
+
|
|
115
|
+
return np.array(self._padding(new_music_seq))
|
|
116
|
+
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def _padding(self, target: list, max_length: int = None):
|
|
120
|
+
if max_length is None:
|
|
121
|
+
max_lengths = []
|
|
122
|
+
for t in target:
|
|
123
|
+
max_lengths.append(len(t))
|
|
124
|
+
max_length = max(max_lengths)
|
|
125
|
+
|
|
126
|
+
print(f"Max length is {max_length}")
|
|
127
|
+
for t in target:
|
|
128
|
+
if len(t) < max_length:
|
|
129
|
+
for _ in range(max_length - len(t)):
|
|
130
|
+
t.append(0)
|
|
131
|
+
return target
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
def save(self, save_directory: str) -> bool:
|
|
135
|
+
if not self.is_error:
|
|
136
|
+
print(f"Result shape is:{self.aya_node.shape}")
|
|
137
|
+
|
|
138
|
+
split_direc = self.directory.split("\\")
|
|
139
|
+
|
|
140
|
+
filename = split_direc[-1].split(".")[0]
|
|
141
|
+
|
|
142
|
+
np.savez(save_directory + "/" + filename, self.aya_node)
|
|
143
|
+
print("処理が正常に終了しました。")
|
|
144
|
+
return True
|
|
145
|
+
else:
|
|
146
|
+
print("Transformerが望むデータ形式ではないため、保存ができませんでした。")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
def ct_aya_node(self, notes: list) -> ndarray[Any, dtype[Any]]:
|
|
150
|
+
node = np.array([self.tokenizer.get(-1, constants.START_SEQ_TOKEN)])
|
|
151
|
+
|
|
152
|
+
back_start = None
|
|
153
|
+
for note in notes:
|
|
154
|
+
|
|
155
|
+
note: Note = note
|
|
156
|
+
start = self.ct_time_to_beat(note.start)
|
|
157
|
+
end = self.ct_time_to_beat(note.end)
|
|
158
|
+
|
|
159
|
+
if back_start is not None:
|
|
160
|
+
shift = abs((back_start // 32) - (start // 32))
|
|
161
|
+
if 4 > shift:
|
|
162
|
+
#if shift > 0:
|
|
163
|
+
node = np.append(node, self.tokenizer.get(shift, tr.SHIFT_TYPE)) #何小節のブランクができたかを計算
|
|
164
|
+
else:
|
|
165
|
+
node = np.append(node, self.tokenizer.get(-1, constants.END_SEQ_TOKEN))
|
|
166
|
+
node = np.append(node, self.tokenizer.get(-1, constants.START_SEQ_TOKEN))
|
|
167
|
+
|
|
168
|
+
pitch_token = self.tokenizer.get(note.pitch, tr.PITCH_TYPE)
|
|
169
|
+
velocity_token = self.tokenizer.get(note.velocity, tr.VELOCITY_TYPE)
|
|
170
|
+
duration_token = self.tokenizer.get(min(abs(end - start), 99), tr.DURATION_TYPE)
|
|
171
|
+
start_token = self.tokenizer.get(start % 32, tr.START_TYPE)
|
|
172
|
+
note_token = np.array([start_token, pitch_token, velocity_token, duration_token])
|
|
173
|
+
node = np.concatenate((node, note_token))
|
|
174
|
+
back_start = start
|
|
175
|
+
|
|
176
|
+
node = np.append(node, self.tokenizer.get(-1, constants.END_SEQ_TOKEN))
|
|
177
|
+
return node
|
|
178
|
+
|
|
179
|
+
def _ct_key(self):
|
|
180
|
+
try:
|
|
181
|
+
if self.midi_data is None:
|
|
182
|
+
ct = ConvKey(self.directory, "C")
|
|
183
|
+
ct.convert()
|
|
184
|
+
self.midi_data = ct.midi_data
|
|
185
|
+
if ct.is_Error:
|
|
186
|
+
self.is_error = ct.is_Error
|
|
187
|
+
else:
|
|
188
|
+
ct = ConvKey(directory=self.directory, midi_data=self.midi_data, key="C")
|
|
189
|
+
ct.convert()
|
|
190
|
+
self.midi_data = ct.midi_data
|
|
191
|
+
except OSError:
|
|
192
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
193
|
+
self.is_error = True
|
|
194
|
+
except IndexError:
|
|
195
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
196
|
+
self.is_error = True
|
|
197
|
+
except mido.midifiles.meta.KeySignatureError:
|
|
198
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
199
|
+
self.is_error = True
|
|
200
|
+
except ValueError:
|
|
201
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
202
|
+
self.is_error = True
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _ct_tempo(self):
|
|
207
|
+
try:
|
|
208
|
+
if self.midi_data is None:
|
|
209
|
+
ct = ConvTempo(self.directory, self.tempo)
|
|
210
|
+
ct.convert()
|
|
211
|
+
self.midi_data = ct.midi_data
|
|
212
|
+
if ct.is_Error:
|
|
213
|
+
self.is_error = ct.is_Error
|
|
214
|
+
else:
|
|
215
|
+
ct = ConvTempo(directory=self.directory, midi_data=self.midi_data, change_tempo=120)
|
|
216
|
+
ct.convert()
|
|
217
|
+
self.midi_data = ct.midi_data
|
|
218
|
+
except OSError:
|
|
219
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
220
|
+
self.is_error = True
|
|
221
|
+
except IndexError:
|
|
222
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
223
|
+
self.is_error = True
|
|
224
|
+
except ValueError:
|
|
225
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
226
|
+
self.is_error = True
|
|
227
|
+
|
|
228
|
+
except mido.midifiles.meta.KeySignatureError:
|
|
229
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
230
|
+
self.is_error = True
|
|
231
|
+
|
|
232
|
+
except EOFError:
|
|
233
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
234
|
+
self.is_error = True
|
|
235
|
+
|
|
236
|
+
except KeyError:
|
|
237
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
238
|
+
self.is_error = True
|
|
239
|
+
except ZeroDivisionError:
|
|
240
|
+
print(f"{self.directory}でエラーが発生。処理を中断します。")
|
|
241
|
+
self.is_error = True
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def ct_time_to_beat(self, time: float) -> int:
|
|
246
|
+
b4 = 60 / self.tempo
|
|
247
|
+
b8 = b4 / 2
|
|
248
|
+
b16 = b8 / 2
|
|
249
|
+
b32 = b16 / 2
|
|
250
|
+
|
|
251
|
+
beat, sub = self.calc_time_to_beat(time, b32)
|
|
252
|
+
|
|
253
|
+
return beat
|
|
254
|
+
|
|
255
|
+
def calc_time_to_beat(self, time, beat_time) -> (int, int):
|
|
256
|
+
main_beat: int = time // beat_time
|
|
257
|
+
sub_time: int = time % beat_time
|
|
258
|
+
return main_beat, sub_time
|
|
259
|
+
|
|
260
|
+
def padding(self, array1: ndarray, array2: ndarray):
|
|
261
|
+
print(f"a:{array1.shape[-1]} b:{array2.shape[-1]}")
|
|
262
|
+
|
|
263
|
+
max_len = max(array1.shape[-1], array2.shape[-1])
|
|
264
|
+
|
|
265
|
+
# 0でパディングして同じ長さに揃える
|
|
266
|
+
if len(array1.shape) > 1:
|
|
267
|
+
padded_array1 = np.array([np.pad(row, (0, max_len - len(row)), mode='constant') for row in array1])
|
|
268
|
+
else:
|
|
269
|
+
padded_array1 = np.pad(array1, (0, max_len - len(array1)), mode='constant')
|
|
270
|
+
|
|
271
|
+
if len(array2.shape) > 1:
|
|
272
|
+
padded_array2 = np.array([np.pad(row, (0, max_len - len(row)), mode='constant') for row in array2])
|
|
273
|
+
else:
|
|
274
|
+
padded_array2 = np.pad(array2, (0, max_len - len(array2)), mode='constant')
|
|
275
|
+
|
|
276
|
+
return padded_array1, padded_array2
|
|
277
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PositionalEncoding(nn.Module):
|
|
7
|
+
|
|
8
|
+
def __init__(self, d_model, dropout=0.1, max_len=5000):
|
|
9
|
+
super(PositionalEncoding, self).__init__()
|
|
10
|
+
self.dropout = nn.Dropout(p=dropout)
|
|
11
|
+
|
|
12
|
+
# Positional Encodingのテンソルを生成
|
|
13
|
+
pe = torch.zeros(max_len, d_model)
|
|
14
|
+
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
|
15
|
+
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
|
|
16
|
+
|
|
17
|
+
pe[:, 0::2] = torch.sin(position * div_term)
|
|
18
|
+
pe[:, 1::2] = torch.cos(position * div_term)
|
|
19
|
+
pe = pe.unsqueeze(0).transpose(0, 1)
|
|
20
|
+
|
|
21
|
+
self.register_buffer('pe', pe)
|
|
22
|
+
|
|
23
|
+
def forward(self, x):
|
|
24
|
+
x = x + self.pe[:x.size(0), :]
|
|
25
|
+
return self.dropout(x)
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
PITCH_MAX = 128
|
|
4
|
+
VELO = 128
|
|
5
|
+
LENGTH = 999
|
|
6
|
+
LENGTH_HALF = 999
|
|
7
|
+
BEGIN = 999
|
|
8
|
+
BEGIN_HALF = 999
|
|
9
|
+
ROOT = 99
|
|
10
|
+
START_SEQ_TOKEN = "<S_SEQ>"
|
|
11
|
+
END_SEQ_TOKEN = "<E_SEQ>"
|
|
12
|
+
PADDING_TOKEN = "<PAD>"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_device():
|
|
16
|
+
if torch.cuda.is_available():
|
|
17
|
+
return torch.device('cuda')
|
|
18
|
+
else:
|
|
19
|
+
return torch.device('cpu')
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Tokenizerで変換したシーケンスを全て保管します。
|
|
3
|
+
'''
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import torch
|
|
8
|
+
from torch.utils.data import Dataset
|
|
9
|
+
import numpy as np
|
|
10
|
+
from .constants import get_device
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MORTM_DataSets(Dataset):
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.musics_seq = None
|
|
16
|
+
self.tgt_seq = None
|
|
17
|
+
|
|
18
|
+
def __len__(self):
|
|
19
|
+
return len(self.musics_seq)
|
|
20
|
+
|
|
21
|
+
def __getitem__(self, item):
|
|
22
|
+
return torch.tensor(self.musics_seq[item], dtype=torch.int).to(get_device()), torch.tensor(self.tgt_seq[item], dtype=torch.int).to(get_device())
|
|
23
|
+
|
|
24
|
+
def add_data(self, music_seq: np.ndarray):
|
|
25
|
+
if self.musics_seq is None:
|
|
26
|
+
if len(music_seq.shape) == 1:
|
|
27
|
+
self.musics_seq = [music_seq.tolist()]
|
|
28
|
+
else:
|
|
29
|
+
self.musics_seq = music_seq.tolist()
|
|
30
|
+
else:
|
|
31
|
+
if len(music_seq.shape) == 1:
|
|
32
|
+
music_seq: list = [music_seq.tolist()]
|
|
33
|
+
self.musics_seq = self.musics_seq + music_seq
|
|
34
|
+
else:
|
|
35
|
+
self.musics_seq = self.musics_seq + music_seq.tolist()
|
|
36
|
+
|
|
37
|
+
print(self._get_shape(self.musics_seq))
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def split_seq_data(self):
|
|
41
|
+
new_music_seq = [[]]
|
|
42
|
+
for i in range(len(self.musics_seq)):
|
|
43
|
+
result = []
|
|
44
|
+
current_sublist = []
|
|
45
|
+
for value in self.musics_seq[i]:
|
|
46
|
+
current_sublist.append(value)
|
|
47
|
+
if value == 2:
|
|
48
|
+
result.append(current_sublist)
|
|
49
|
+
current_sublist = []
|
|
50
|
+
new_music_seq = new_music_seq + result
|
|
51
|
+
|
|
52
|
+
self.musics_seq = new_music_seq
|
|
53
|
+
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def set_train_data(self):
|
|
57
|
+
print(f"Set train data....{self._get_shape(self.musics_seq)}")
|
|
58
|
+
for music_seq in self.musics_seq:
|
|
59
|
+
tgt_data = []
|
|
60
|
+
for i in range(len(music_seq) - 1):
|
|
61
|
+
tgt_data.append(music_seq[i + 1])
|
|
62
|
+
if self.tgt_seq is None:
|
|
63
|
+
self.tgt_seq = [tgt_data]
|
|
64
|
+
else:
|
|
65
|
+
self.tgt_seq = self.tgt_seq + [tgt_data]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
print(f"clear! train shape is:{self._get_shape(self.tgt_seq)}")
|
|
69
|
+
|
|
70
|
+
def _get_shape(self, lst):
|
|
71
|
+
if isinstance(lst, list):
|
|
72
|
+
return [len(lst)] + self._get_shape(lst[0]) if lst else []
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
def set_padding(self):
|
|
76
|
+
# self._padding(self.tgt_seq)
|
|
77
|
+
self._padding(self.musics_seq)
|
|
78
|
+
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
def get_max_length(self, target):
|
|
82
|
+
max_lengths = []
|
|
83
|
+
for t in target:
|
|
84
|
+
max_lengths.append(len(t))
|
|
85
|
+
max_length = max(max_lengths)
|
|
86
|
+
return max_length
|
|
87
|
+
|
|
88
|
+
def _padding(self, target: list):
|
|
89
|
+
max_lengths = []
|
|
90
|
+
for t in target:
|
|
91
|
+
max_lengths.append(len(t))
|
|
92
|
+
max_length = max(max_lengths)
|
|
93
|
+
print(f"Max length is {max_length}")
|
|
94
|
+
for t in target:
|
|
95
|
+
if len(t) < max_length:
|
|
96
|
+
for _ in range(max_length - len(t)):
|
|
97
|
+
t.append(0)
|
|
98
|
+
pass
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import base64
|
|
3
|
+
|
|
4
|
+
from mortm.messager import Messenger
|
|
5
|
+
from google.auth.transport.requests import Request
|
|
6
|
+
from google.oauth2.credentials import Credentials
|
|
7
|
+
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
8
|
+
from googleapiclient.discovery import build
|
|
9
|
+
from googleapiclient.errors import HttpError
|
|
10
|
+
from email.mime.text import MIMEText
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Gmail APIのスコープを設定
|
|
14
|
+
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GmailMessanger(Messenger):
|
|
18
|
+
|
|
19
|
+
def __init__(self, token_file: str, client_secret_file: str, send_address):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.token_file = token_file
|
|
22
|
+
self.client_secret_file = client_secret_file
|
|
23
|
+
self.send_address = send_address
|
|
24
|
+
self.creds = self.authenticate_gmail()
|
|
25
|
+
|
|
26
|
+
def authenticate_gmail(self):
|
|
27
|
+
creds = None
|
|
28
|
+
if os.path.exists(self.token_file):
|
|
29
|
+
creds = Credentials.from_authorized_user_file(self.token_file, SCOPES)
|
|
30
|
+
if not creds or not creds.valid:
|
|
31
|
+
if creds and creds.expired and creds.refresh_token:
|
|
32
|
+
creds.refresh(Request())
|
|
33
|
+
else:
|
|
34
|
+
flow = InstalledAppFlow.from_client_secrets_file(self.client_secret_file, SCOPES)
|
|
35
|
+
creds = flow.run_local_server(port=0)
|
|
36
|
+
# 認証情報を保存
|
|
37
|
+
with open(self.token_file, 'w') as token:
|
|
38
|
+
token.write(creds.to_json())
|
|
39
|
+
return creds
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_message(self, to, subject, message_text):
|
|
43
|
+
message = MIMEText(message_text)
|
|
44
|
+
message['to'] = to
|
|
45
|
+
message['subject'] = subject
|
|
46
|
+
return {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _send_email(self, service, user_id, message):
|
|
50
|
+
try:
|
|
51
|
+
message = service.users().messages().send(userId=user_id, body=message).execute()
|
|
52
|
+
print(f'Message Id: {message["id"]}')
|
|
53
|
+
return message
|
|
54
|
+
except HttpError as error:
|
|
55
|
+
print(f'An error occurred: {error}')
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def send_message(self, subject: str, body: str):
|
|
60
|
+
# 認証を実行してGmail APIサービスを取得
|
|
61
|
+
service = build('gmail', 'v1', credentials=self.creds)
|
|
62
|
+
|
|
63
|
+
message = self.create_message(self.send_address, subject, body)
|
|
64
|
+
|
|
65
|
+
# メールを送信
|
|
66
|
+
self._send_email(service, 'me', message)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch import Tensor
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from .PositionalEncoding import PositionalEncoding
|
|
5
|
+
|
|
6
|
+
from .constants import get_device
|
|
7
|
+
|
|
8
|
+
device = get_device()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MORTM(nn.Module):
|
|
12
|
+
token_dict = {
|
|
13
|
+
0: "600_633",
|
|
14
|
+
1: "10_139",
|
|
15
|
+
2: "300_429",
|
|
16
|
+
3: "500_600",
|
|
17
|
+
4: "600_605"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def __init__(self, vocab_size, trans_layer=6, num_heads=8, d_model=512, dim_feedforward=1024, dropout=0.1,
|
|
21
|
+
position_length=2048):
|
|
22
|
+
super(MORTM, self).__init__()
|
|
23
|
+
|
|
24
|
+
self.trans_layer = trans_layer
|
|
25
|
+
self.num_heads = num_heads
|
|
26
|
+
self.d_model = d_model
|
|
27
|
+
self.dim_feedforward = dim_feedforward
|
|
28
|
+
self.dropout = dropout
|
|
29
|
+
|
|
30
|
+
#位置エンコーディングを作成
|
|
31
|
+
self.positional: PositionalEncoding = PositionalEncoding(self.d_model, dropout, position_length).to(device)
|
|
32
|
+
#Transformerの設定
|
|
33
|
+
self.transformer: nn.Transformer = nn.Transformer(d_model=self.d_model, nhead=num_heads, #各種パラメーターの設計
|
|
34
|
+
num_encoder_layers=self.trans_layer,
|
|
35
|
+
num_decoder_layers=self.trans_layer,
|
|
36
|
+
dropout=self.dropout, dim_feedforward=dim_feedforward,
|
|
37
|
+
).to(device)
|
|
38
|
+
print(f"Input Vocab Size:{vocab_size}")
|
|
39
|
+
self.Wout = nn.Linear(self.d_model, vocab_size).to(device)
|
|
40
|
+
|
|
41
|
+
self.embedding: nn.Embedding = nn.Embedding(vocab_size, self.d_model).to(device)
|
|
42
|
+
self.softmax: nn.Softmax = nn.Softmax(dim=-1).to(device)
|
|
43
|
+
|
|
44
|
+
def forward(self, inputs_seq, tgt_seq, input_mask, tgt_mask, input_padding_mask, tgt_padding_mask):
|
|
45
|
+
|
|
46
|
+
inputs_em: Tensor = self.embedding(inputs_seq)
|
|
47
|
+
inputs_em = inputs_em.permute(1, 0, 2)
|
|
48
|
+
inputs_pos: Tensor = self.positional(inputs_em)
|
|
49
|
+
|
|
50
|
+
if tgt_seq is None:
|
|
51
|
+
tgt_pos = inputs_pos
|
|
52
|
+
else:
|
|
53
|
+
tgt_em: Tensor = self.embedding(tgt_seq)
|
|
54
|
+
tgt_em = tgt_em.permute(1, 0, 2)
|
|
55
|
+
tgt_pos: Tensor = self.positional(tgt_em)
|
|
56
|
+
|
|
57
|
+
#print(inputs_pos.shape, tgt_pos.shape)
|
|
58
|
+
out: Tensor = self.transformer(inputs_pos, tgt_pos, input_mask, tgt_mask,
|
|
59
|
+
src_key_padding_mask=input_padding_mask, tgt_key_padding_mask=tgt_padding_mask)
|
|
60
|
+
|
|
61
|
+
out.permute(1, 0, 2)
|
|
62
|
+
|
|
63
|
+
score = self.Wout(out)
|
|
64
|
+
return score
|
|
65
|
+
|
|
66
|
+
def generate_by_length(self, input_seq, max_length, p=0.9, temperature=0.1):
|
|
67
|
+
self.eval()
|
|
68
|
+
output = torch.tensor([input_seq], dtype=torch.long).unsqueeze(1).to(device)
|
|
69
|
+
for _ in range(max_length):
|
|
70
|
+
with torch.no_grad():
|
|
71
|
+
output = self._next_note_token(output)
|
|
72
|
+
|
|
73
|
+
return output
|
|
74
|
+
|
|
75
|
+
def _next_note_token(self, output, p=0.9, temperature=0.1):
|
|
76
|
+
isEnd = False
|
|
77
|
+
token_count = 0
|
|
78
|
+
while not isEnd:
|
|
79
|
+
mask = self.transformer.generate_square_subsequent_mask(output.shape[1]).to(device)
|
|
80
|
+
outputs = self(output, output, mask, mask, None, None)
|
|
81
|
+
logits = outputs[:, -1, :]
|
|
82
|
+
|
|
83
|
+
str_token_duration = self.token_dict[token_count]
|
|
84
|
+
parts = str_token_duration.split("_")
|
|
85
|
+
token_duration = [int(part) for part in parts]
|
|
86
|
+
if token_count != 4:
|
|
87
|
+
token = self._next_token(logits[:, token_duration[0]:token_duration[1]])
|
|
88
|
+
token += token_duration[0]
|
|
89
|
+
else:
|
|
90
|
+
token = self._next_token(logits)
|
|
91
|
+
isEnd = True
|
|
92
|
+
token_count += 1
|
|
93
|
+
|
|
94
|
+
output = torch.cat((output.flatten(), token.unsqueeze(0))).unsqueeze(0).to(device)
|
|
95
|
+
|
|
96
|
+
return output
|
|
97
|
+
|
|
98
|
+
def _next_token(self, end_logit_score: Tensor, p=0.9, temperature=0.1):
|
|
99
|
+
end_logit_score = end_logit_score / temperature
|
|
100
|
+
|
|
101
|
+
sorted_logits, sorted_indices = torch.sort(end_logit_score, descending=True)
|
|
102
|
+
cumulative_probs = torch.cumsum(self.softmax(sorted_logits), dim=-1)
|
|
103
|
+
sorted_indices_to_remove = cumulative_probs > p
|
|
104
|
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
|
105
|
+
sorted_indices_to_remove[..., 0] = 0
|
|
106
|
+
indices_to_remove = sorted_indices[sorted_indices_to_remove]
|
|
107
|
+
end_logit_score[:, indices_to_remove] = -float('Inf')
|
|
108
|
+
|
|
109
|
+
score = self.softmax(end_logit_score)[-1]
|
|
110
|
+
dis = torch.distributions.categorical.Categorical(probs=score)
|
|
111
|
+
next_token = dis.sample()
|
|
112
|
+
|
|
113
|
+
#print(next_token)
|
|
114
|
+
return next_token
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def top_p_sampling(self, input_ids, tokenizer, p=0.9, max_length=20, temperature=0.2):
|
|
118
|
+
self.eval()
|
|
119
|
+
output = torch.tensor([input_ids], dtype=torch.long).unsqueeze(1).to(device)
|
|
120
|
+
for _ in range(max_length):
|
|
121
|
+
with torch.no_grad():
|
|
122
|
+
mask = self.transformer.generate_square_subsequent_mask(output.shape[1]).to(device)
|
|
123
|
+
outputs = self(output, output, mask, mask, None, None)
|
|
124
|
+
logits = outputs[:, -1, :]
|
|
125
|
+
|
|
126
|
+
print(logits[-1, 10:138])
|
|
127
|
+
|
|
128
|
+
logits = logits / temperature
|
|
129
|
+
|
|
130
|
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
|
131
|
+
cumulative_probs = torch.cumsum(self.softmax(sorted_logits), dim=-1)
|
|
132
|
+
sorted_indices_to_remove = cumulative_probs > p
|
|
133
|
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
|
134
|
+
sorted_indices_to_remove[..., 0] = 0
|
|
135
|
+
indices_to_remove = sorted_indices[sorted_indices_to_remove]
|
|
136
|
+
logits[:, indices_to_remove] = -float('Inf')
|
|
137
|
+
|
|
138
|
+
probabilities = self.softmax(logits)[-1]
|
|
139
|
+
dis = torch.distributions.categorical.Categorical(probs=probabilities)
|
|
140
|
+
next_token = dis.sample()
|
|
141
|
+
# バッチサイズを一致させるために次元を調整
|
|
142
|
+
next_token = next_token.unsqueeze(0)
|
|
143
|
+
output = torch.cat((output.flatten(), next_token)).unsqueeze(0).to(device)
|
|
144
|
+
#print(output)
|
|
145
|
+
return output.tolist()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class DummyDecoder(nn.Module):
|
|
151
|
+
def __init__(self):
|
|
152
|
+
super(DummyDecoder, self).__init__()
|
|
153
|
+
|
|
154
|
+
def forward(self, tgt, memory, tgt_mask, memory_mask, tgt_key_padding_mask, memory_key_padding_mask, **kwargs):
|
|
155
|
+
return memory
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from . import constants
|
|
4
|
+
|
|
5
|
+
'''旋律トークン'''
|
|
6
|
+
PITCH_TYPE = 'p'
|
|
7
|
+
VELOCITY_TYPE = 'v'
|
|
8
|
+
DURATION_TYPE = 'd'
|
|
9
|
+
|
|
10
|
+
'''指示トークン'''
|
|
11
|
+
START_TYPE = 's'
|
|
12
|
+
SHIFT_TYPE = 'h'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Tokenizer:
|
|
16
|
+
def __init__(self, save_directory: str, load_data: str = None):
|
|
17
|
+
self.save_directory = save_directory
|
|
18
|
+
if load_data is None:
|
|
19
|
+
# 特殊トークン
|
|
20
|
+
self.special_token_position = 3
|
|
21
|
+
#旋律トークン
|
|
22
|
+
self.pitch_position = 10
|
|
23
|
+
self.velocity_position = 300
|
|
24
|
+
self.duration_position = 500
|
|
25
|
+
#指示トークン
|
|
26
|
+
self.instruction_start_position = 600
|
|
27
|
+
self.instruction_shift_position = 650
|
|
28
|
+
|
|
29
|
+
self.tokens: dict = dict()
|
|
30
|
+
self.token_max: dict = self._init_mx_dict()
|
|
31
|
+
self.tokens[constants.PADDING_TOKEN] = 0
|
|
32
|
+
self.tokens[constants.START_SEQ_TOKEN] = 1
|
|
33
|
+
self.tokens[constants.END_SEQ_TOKEN] = 2
|
|
34
|
+
else:
|
|
35
|
+
with open(load_data, 'r') as file:
|
|
36
|
+
self.tokens: dict = json.load(file)
|
|
37
|
+
self.rev_tokens: dict = {v: k for k, v in self.tokens.items()}
|
|
38
|
+
|
|
39
|
+
def _init_mx_dict(self) -> dict:
|
|
40
|
+
my_dict = dict()
|
|
41
|
+
for i in range(0, 654):
|
|
42
|
+
my_dict[i] = 0
|
|
43
|
+
return my_dict
|
|
44
|
+
|
|
45
|
+
def rev_get(self, a):
|
|
46
|
+
return self.rev_tokens[a]
|
|
47
|
+
|
|
48
|
+
def get(self, a: int, token_type: str, b = False):
|
|
49
|
+
if a == -1:
|
|
50
|
+
b = True
|
|
51
|
+
my_token = token_type
|
|
52
|
+
else:
|
|
53
|
+
my_token = f"{token_type}_{a}"
|
|
54
|
+
|
|
55
|
+
if my_token in self.tokens:
|
|
56
|
+
if not b:
|
|
57
|
+
self.token_max[self.tokens[my_token]] += 1
|
|
58
|
+
return self.tokens[my_token]
|
|
59
|
+
else:
|
|
60
|
+
if token_type is PITCH_TYPE:
|
|
61
|
+
self.tokens[my_token] = self.pitch_position
|
|
62
|
+
self.pitch_position += 1
|
|
63
|
+
|
|
64
|
+
elif token_type is VELOCITY_TYPE:
|
|
65
|
+
self.tokens[my_token] = self.velocity_position
|
|
66
|
+
self.velocity_position += 1
|
|
67
|
+
|
|
68
|
+
elif token_type is DURATION_TYPE:
|
|
69
|
+
self.tokens[my_token] = self.duration_position
|
|
70
|
+
self.duration_position += 1
|
|
71
|
+
|
|
72
|
+
elif token_type is START_TYPE:
|
|
73
|
+
self.tokens[my_token] = self.instruction_start_position
|
|
74
|
+
self.instruction_start_position += 1
|
|
75
|
+
|
|
76
|
+
elif token_type is SHIFT_TYPE:
|
|
77
|
+
self.tokens[my_token] = self.instruction_shift_position
|
|
78
|
+
self.instruction_shift_position += 1
|
|
79
|
+
|
|
80
|
+
self.token_max[self.tokens[my_token]] = 1
|
|
81
|
+
return self.tokens[my_token]
|
|
82
|
+
|
|
83
|
+
def save(self):
|
|
84
|
+
json_string = json.dumps(self.tokens)
|
|
85
|
+
with open(self.save_directory + "/vocab_list.json", 'w') as file:
|
|
86
|
+
file.write(json_string)
|
|
87
|
+
|
|
88
|
+
json_s = json.dumps(self.token_max)
|
|
89
|
+
with open(self.save_directory + "/vocab_max.json", 'w') as file:
|
|
90
|
+
file.write(json_s)
|
|
91
|
+
|
|
92
|
+
pass
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
'''
|
|
2
|
+
MORTMの学習を行う際にこのモジュールを使います。
|
|
3
|
+
train_mortmメソッドを呼び出し、引数の型に合ったオブジェクトを代入してください。
|
|
4
|
+
最低でも、「データセット(Tokenizerで変換したもの)のディレクトリ」、「モデルの出力先のディレクトリ」,
|
|
5
|
+
「モデルのバージョン」,「ボキャブラリーサイズ」,「エポック回数」、「各トークンの出現回数のリスト」が必要です。
|
|
6
|
+
'''
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
from torch import Tensor
|
|
15
|
+
from torch.utils.data import DataLoader
|
|
16
|
+
import torch.nn as nn
|
|
17
|
+
import numpy as np
|
|
18
|
+
from .messager import Messenger
|
|
19
|
+
|
|
20
|
+
from .datasets import MORTM_DataSets
|
|
21
|
+
from .mortm import MORTM
|
|
22
|
+
from .constants import get_device
|
|
23
|
+
|
|
24
|
+
IS_DEBUG = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _send_prediction_end_time(message, loader_len, begin_time, end_time,
|
|
28
|
+
vocab_size: int, num_epochs: int, trans_layer, num_heads, d_model,
|
|
29
|
+
dim_feedforward, dropout, position_length):
|
|
30
|
+
t = end_time - begin_time
|
|
31
|
+
end_time_progress = (t * loader_len * num_epochs) / 3600
|
|
32
|
+
message.send_message("終了見込みについて",
|
|
33
|
+
f"現在学習が進行しています。\n"
|
|
34
|
+
f"今回設定したパラメータに基づいて終了時刻を計算しました。\n"
|
|
35
|
+
f"ボキャブラリーサイズ:{vocab_size}\n"
|
|
36
|
+
f"エポック回数:{num_epochs}\n"
|
|
37
|
+
f"Transformerのレイヤー層:{trans_layer}\n"
|
|
38
|
+
f"Modelの次元数:{d_model}\n"
|
|
39
|
+
f"シーケンスの長さ:{dim_feedforward}\n"
|
|
40
|
+
f"ドロップアウト:{dropout}\n"
|
|
41
|
+
f"\n\n シーケンスの1回目の処理が終了しました。かかった時間は{t:.1f}秒でした。\n"
|
|
42
|
+
f"終了見込み時間は{end_time_progress:.2f}時間です"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# デバイスを取得
|
|
47
|
+
def _set_train_data(directory, datasets):
|
|
48
|
+
if not IS_DEBUG:
|
|
49
|
+
print("Generating TrainData.....")
|
|
50
|
+
t_data = MORTM_DataSets()
|
|
51
|
+
for dataset in datasets:
|
|
52
|
+
print(f"Load [{directory + dataset}]")
|
|
53
|
+
np_load_data = np.load(directory + dataset)
|
|
54
|
+
train_data = None
|
|
55
|
+
for i in range(len(np_load_data)):
|
|
56
|
+
|
|
57
|
+
np_data = np.expand_dims(np_load_data[f'arr_{i}'], axis=0)[0]
|
|
58
|
+
print(np_data.shape)
|
|
59
|
+
|
|
60
|
+
if train_data is None:
|
|
61
|
+
train_data = np_data
|
|
62
|
+
else:
|
|
63
|
+
train_data = np.concatenate((train_data, np_data), axis=0)
|
|
64
|
+
print(f"最初の5音:{train_data[-1][0:25]}")
|
|
65
|
+
t_data.add_data(train_data)
|
|
66
|
+
print(f"Token size: {sum(len(sub) for sub in t_data.musics_seq)} ")
|
|
67
|
+
print("----------------------------------")
|
|
68
|
+
#t_data.split_seq_data()
|
|
69
|
+
#t_data.set_padding()
|
|
70
|
+
t_data.set_train_data()
|
|
71
|
+
|
|
72
|
+
return t_data
|
|
73
|
+
else:
|
|
74
|
+
np_load_data = np.load(directory + datasets[0])
|
|
75
|
+
print(np_load_data[f'arr_{3}'])
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_padding_mask(input_ids):
|
|
80
|
+
pad_id = None
|
|
81
|
+
for inputs in input_ids:
|
|
82
|
+
pad = []
|
|
83
|
+
for token in inputs:
|
|
84
|
+
if token == 0:
|
|
85
|
+
pad.append(0)
|
|
86
|
+
else:
|
|
87
|
+
pad.append(1)
|
|
88
|
+
if pad_id is None:
|
|
89
|
+
pad_id = [pad]
|
|
90
|
+
else:
|
|
91
|
+
pad_id = pad_id + [pad]
|
|
92
|
+
padding_mask = torch.tensor(pad_id, dtype=torch.float).to(get_device())
|
|
93
|
+
return padding_mask
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _train(ayato_dataset, message: Messenger, vocab_size: int, num_epochs: int, weight: Tensor, trans_layer=6,
|
|
97
|
+
num_heads=8, d_model=512, dim_feedforward=1024, dropout=0.1,
|
|
98
|
+
position_length=2048, accumulation_steps=4, batch_size=16):
|
|
99
|
+
loader = DataLoader(ayato_dataset, batch_size=batch_size, shuffle=True, pin_memory=False)
|
|
100
|
+
print("Creating Model....")
|
|
101
|
+
model = MORTM(vocab_size=vocab_size, trans_layer=trans_layer, num_heads=num_heads,
|
|
102
|
+
d_model=d_model, dim_feedforward=dim_feedforward,
|
|
103
|
+
dropout=dropout, position_length=position_length).to(get_device())
|
|
104
|
+
|
|
105
|
+
criterion = nn.CrossEntropyLoss(ignore_index=0, weight=weight.to(get_device())) # 損失関数を定義
|
|
106
|
+
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=0.01) # オプティマイザを定義
|
|
107
|
+
|
|
108
|
+
print("Start training...")
|
|
109
|
+
|
|
110
|
+
loss_val = None
|
|
111
|
+
mail_bool = True
|
|
112
|
+
for epoch in range(num_epochs):
|
|
113
|
+
print(f"epoch {epoch + 1} start....")
|
|
114
|
+
print(f"batch size :{len(loader)}")
|
|
115
|
+
count = 1
|
|
116
|
+
epoch_loss = 0.0
|
|
117
|
+
|
|
118
|
+
model.train()
|
|
119
|
+
optimizer.zero_grad()
|
|
120
|
+
|
|
121
|
+
for input_ids, targets in loader: # seqにはbatch_size分の楽曲が入っている
|
|
122
|
+
print(f"learning sequence {count}")
|
|
123
|
+
begin_time = time.time()
|
|
124
|
+
input_ids.to(get_device())
|
|
125
|
+
#inputs_mask = model.mortm_X.generate_square_subsequent_mask(input_ids.shape[1]).to(device)
|
|
126
|
+
targets_mask = model.transformer.generate_square_subsequent_mask(targets.shape[1]).to(get_device())
|
|
127
|
+
padding_mask_in: Tensor = _get_padding_mask(input_ids)
|
|
128
|
+
padding_mask_tgt: Tensor = _get_padding_mask(targets)
|
|
129
|
+
|
|
130
|
+
output = model(input_ids, targets, None, targets_mask, padding_mask_in, padding_mask_tgt)
|
|
131
|
+
|
|
132
|
+
outputs = output.view(-1, output.size(-1))
|
|
133
|
+
targets = targets.view(-1).long()
|
|
134
|
+
|
|
135
|
+
loss = criterion(outputs, targets) # 損失を計算
|
|
136
|
+
loss.backward() # 逆伝播
|
|
137
|
+
|
|
138
|
+
if count % accumulation_steps == 0: #実質バッチサイズは64である
|
|
139
|
+
optimizer.step() # オプティマイザを更新
|
|
140
|
+
optimizer.zero_grad()
|
|
141
|
+
print("Optimizerを更新しました。")
|
|
142
|
+
|
|
143
|
+
epoch_loss += loss.item()
|
|
144
|
+
count += 1
|
|
145
|
+
end_time = time.time()
|
|
146
|
+
|
|
147
|
+
if mail_bool and message is not None:
|
|
148
|
+
_send_prediction_end_time(message, len(loader), begin_time, end_time, vocab_size, num_epochs,
|
|
149
|
+
trans_layer, num_heads, d_model, dim_feedforward, dropout, position_length)
|
|
150
|
+
mail_bool = False
|
|
151
|
+
|
|
152
|
+
if (count + 1) % 100 == 0 and message is not None:
|
|
153
|
+
message.send_message("機械学習の途中経過について", f"Epoch {epoch + 1}/{num_epochs}の"
|
|
154
|
+
f"learning sequence {count}結果は、\n {epoch_loss / count:.4f}でした。")
|
|
155
|
+
print(epoch_loss)
|
|
156
|
+
|
|
157
|
+
print(f"Epoch [{epoch + 1}/{num_epochs}], Loss: {epoch_loss:.4f}")
|
|
158
|
+
if message is not None:
|
|
159
|
+
message.send_message("機械学習の途中経過について",
|
|
160
|
+
f"Epoch {epoch + 1}/{num_epochs}の結果は、{epoch_loss / count:.4f}でした。")
|
|
161
|
+
loss_val = epoch_loss / count
|
|
162
|
+
return model, loss_val
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def train_mortm(dataset_directory, save_directory, version: str, vocab_size: int, num_epochs: int, weight_directory,
|
|
166
|
+
message: Messenger = None,
|
|
167
|
+
trans_layer=12, num_heads=8, d_model=1024,
|
|
168
|
+
dim_feedforward=2048, dropout=0.2, position_length=2048,
|
|
169
|
+
accumulation_steps=4, batch_size=16):
|
|
170
|
+
os.environ['CUDA_LAUNCH_BLOCKING'] = '1'
|
|
171
|
+
today_date = datetime.date.today().strftime('%Y%m%d')
|
|
172
|
+
|
|
173
|
+
print(f"ToDay is{datetime.date.today()}! start generating MORTEM_Model.{version}_{today_date}")
|
|
174
|
+
|
|
175
|
+
datasets = os.listdir(dataset_directory)
|
|
176
|
+
train_data = _set_train_data(dataset_directory, datasets)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
with open(weight_directory, 'r') as file:
|
|
180
|
+
freq_dict = json.load(file)
|
|
181
|
+
# 逆数を取り、頻出度が0の場合は小さい値に設定
|
|
182
|
+
epsilon = 1e-10 # 非ゼロの小さい値を設定しておく
|
|
183
|
+
weights = []
|
|
184
|
+
|
|
185
|
+
for i in range(len(freq_dict)):
|
|
186
|
+
freq = freq_dict[str(i)] # JSONのキーは文字列なのでstrに変換
|
|
187
|
+
if freq == 0:
|
|
188
|
+
weights.append(epsilon)
|
|
189
|
+
else:
|
|
190
|
+
weights.append(1.0 / freq)
|
|
191
|
+
# テンソルに変換
|
|
192
|
+
weight_tensor = torch.tensor(weights)
|
|
193
|
+
weight_tensor = weight_tensor / weight_tensor.sum()
|
|
194
|
+
print(weight_tensor[650:653])
|
|
195
|
+
model, loss = _train(train_data, message, vocab_size, num_epochs, weight_tensor,
|
|
196
|
+
d_model=d_model,
|
|
197
|
+
dim_feedforward=dim_feedforward,
|
|
198
|
+
trans_layer=trans_layer,
|
|
199
|
+
num_heads=num_heads,
|
|
200
|
+
position_length=position_length,
|
|
201
|
+
dropout=dropout,
|
|
202
|
+
accumulation_steps=accumulation_steps,
|
|
203
|
+
batch_size=batch_size
|
|
204
|
+
) # 20エポック分機械学習を行う。
|
|
205
|
+
|
|
206
|
+
if message is not None:
|
|
207
|
+
message.send_message("機械学習終了のお知らせ",
|
|
208
|
+
f"MORTM.{version}の機械学習が終了しました。 \n 結果の報告です。\n 損失関数: {loss}")
|
|
209
|
+
|
|
210
|
+
torch.save(model.state_dict(), f"{save_directory}/MORTM.{version}_{loss}.pth") # できたモデルをセーブする
|
|
211
|
+
|
|
212
|
+
return model
|
|
213
|
+
|
|
214
|
+
except torch.cuda.OutOfMemoryError:
|
|
215
|
+
if message is not None:
|
|
216
|
+
message.send_message("エラーが発生し、処理を中断しました",
|
|
217
|
+
"学習中にモデルがこのPCのメモリーの理論値を超えました。\nバッチサイズを調整してください")
|
|
218
|
+
|
|
219
|
+
pass
|
mortm-0.11.0/setup.cfg
ADDED
mortm-0.11.0/setup.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='MORTM',
|
|
5
|
+
version='0.11.0',
|
|
6
|
+
author='Nagoshi Takaaki',
|
|
7
|
+
author_email='nagoshi@kthrlab.jp',
|
|
8
|
+
description='音楽の旋律生成を実現したシステム',
|
|
9
|
+
long_description=open('README.md').read(),
|
|
10
|
+
long_description_content_type='text/markdown',
|
|
11
|
+
url='https://github.com/Ayato964',
|
|
12
|
+
packages=['mortm'],
|
|
13
|
+
classifiers=[
|
|
14
|
+
'Programming Language :: Python :: 3',
|
|
15
|
+
'License :: OSI Approved :: MIT License',
|
|
16
|
+
'Operating System :: OS Independent',
|
|
17
|
+
],
|
|
18
|
+
python_requires='>=3.0',
|
|
19
|
+
)
|