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.
@@ -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
+ 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,11 @@
1
+ from abc import abstractmethod
2
+
3
+
4
+ class Messenger:
5
+
6
+ def __init__(self):
7
+ pass
8
+
9
+ @abstractmethod
10
+ def send_message(self, subject: str, body: str):
11
+ pass
@@ -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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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
+ )