<jupyter_start><jupyter_text>LSTM на оригинальном датасетеПопытка сделать монофонический выход из сетки<jupyter_code>import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

import numpy as np<jupyter_output><empty_output><jupyter_text>Сделаем также пользовательский импорт<jupyter_code>from decode_patterns import data_conversion<jupyter_output><empty_output><jupyter_text>Загружаем датасет<jupyter_code># import dataset
drum, bass = data_conversion.make_lstm_dataset(height=16, patterns_file="decode_patterns/train.tsv", mono=True)


# define shuffling of dataset
def shuffle(A, B, p=0.8):
    # take 80% to training, other to testing
    L = len(A)
    idx = np.arange(L) < p*L
    np.random.shuffle(idx)
    yield A[idx]
    yield B[idx]
    yield A[np.logical_not(idx)]
    yield B[np.logical_not(idx)]
    
# we can shuffle train and test set like this:
drum_train, bass_train, drum_test, bass_test = shuffle(drum, bass)

# selecting a validation set
drum_validation, bass_validation = data_conversion.make_lstm_dataset(height=16
                                                        , patterns_file="decode_patterns/validation.tsv"
                                                        , mono=True)
drum_validation = torch.tensor(drum_validation, dtype=torch.float)
bass_validation = torch.tensor(bass_validation, dtype=torch.float)
    
drum_train.shape<jupyter_output><empty_output><jupyter_text>Модель определим в самом простом варианте, который только можно себе представить -- как в примере с конечным автоматом<jupyter_code># попробуем определить модель LSTM как конечный автомат
class DrumNBassLSTM(nn.Module):
    def __init__(self):
        super(DrumNBassLSTM, self).__init__()
        # one input neuron, one output neuron, one layer in LSTM block
        self.input_size = 14
        self.hidden_size = 34
        self.layer_count = 1
        self.lstm = nn.LSTM(self.input_size, self.hidden_size, self.layer_count)
        self.embed_layer = nn.Linear(self.hidden_size, 1)
        self.sigm = nn.Sigmoid()
    
    def forward(self, input):
        # пусть в input у нас приходит вектор размерности (64, 32, 14)
        # то есть 64 отсчёта, тридцать два примера (минибатч), 14 значение в каждом (барабанная партия)
        output, _ = self.lstm(input)
        output = self.sigm(self.embed_layer(output))*37
        return output
# часть обучения
dnb_lstm = DrumNBassLSTM()

criterion = nn.MSELoss()

# для сравнения моделей необходим reconstruction_loss
reconstruction_loss = criterion

# оценим также и разнообразие мелодии по её.. дисперсии?)
# def melody_variety(melody):
#     return 1/(1 + (melody.sum(axis=2) > 1).int())
    
# criterion = nn.NLLLoss() # -- этот товарищ требует, чтобы LSTM выдавал классы,
# criterion = nn.CrossEntropyLoss() # и этот тоже
# (числа от 0 до C-1), но как всё-таки его заставить это делать?...
# optimizer = optim.SGD(dnb_lstm.parameters(), lr=0.001, momentum=0.9)
optimizer = optim.Adam(dnb_lstm.parameters(), lr=0.001)<jupyter_output><empty_output><jupyter_text>Найденные баги и их решения:https://stackoverflow.com/questions/56741087/how-to-fix-runtimeerror-expected-object-of-scalar-type-float-but-got-scalar-typhttps://stackoverflow.com/questions/49206550/pytorch-error-multi-target-not-supported-in-crossentropyloss/49209628https://stackoverflow.com/questions/56243672/expected-target-size-50-88-got-torch-size50-288-88<jupyter_code>epoch_count = 72
batch_size = 32
shuffle_every_epoch = True
    
if shuffle_every_epoch:
    print(f"shuffle_every_epoch is on")
else:
    print(f"shuffle_every_epoch is off")
    # shuffle train and test set:
    drum_train, bass_train, drum_test, bass_test = shuffle(drum, bass)
    drum_train = torch.tensor(drum_train, dtype=torch.float)
    bass_train = torch.tensor(bass_train, dtype=torch.float)
    drum_test = torch.tensor(drum_test, dtype=torch.float)
    drum_test = torch.tensor(drum_test, dtype=torch.float)
        
for epoch in range(epoch_count):  # loop over the dataset multiple times
    print(f"Epoch #{epoch}")
    if shuffle_every_epoch:
        # shuffle train and test set:
        drum_train, bass_train, drum_test, bass_test = shuffle(drum, bass)
        drum_train = torch.tensor(drum_train, dtype=torch.float)
        bass_train = torch.tensor(bass_train, dtype=torch.float)
        drum_test = torch.tensor(drum_test, dtype=torch.float)
        bass_test = torch.tensor(bass_test, dtype=torch.float)
        
    examples_count = drum_train.size()[0]
    examples_id = 0
    
    running_loss = 0.0
    runnint_count = 0
    batch_id = 0
    while examples_id < examples_count:
        batch_drum_train = drum_train[examples_id:examples_id + batch_size,:,:].transpose(0,1)
        batch_bass_train = bass_train[examples_id:examples_id + batch_size,].transpose(0,1)
        # transpose нужен для обмена размерности батча и размерности шагов
#         print(f"batch_drum_train:{batch_drum_train.size()}, batch_bass_train:{batch_bass_train.size()}")

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        bass_outputs = dnb_lstm(batch_drum_train).squeeze()
        
        # loss = criterion(bass_outputs, batch_bass_train.long())
        loss = criterion(bass_outputs, batch_bass_train)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()
        runnint_count += 1
        period = 5
        if batch_id % period == 0 or examples_id + batch_size >= examples_count:
            print('[%d, %5d] train loss: %.7f' %
                  (epoch + 1, batch_id + 1, running_loss / runnint_count))
            running_loss = 0.0
            runnint_count = 1
            
        # update batch info
        examples_id += batch_size
        batch_id += 1
        
    # here we can insert measure error on test set
    with torch.no_grad():
        bass_outputs = dnb_lstm(drum_test).squeeze()
        
        test_count = len(drum_test)
        test_loss = 0
        for k in range(test_count):
            test_loss += reconstruction_loss(bass_outputs[k], bass_test[k])
        print(f"#{epoch + 1} reconstruction test loss: {test_loss/test_count}")
    

#should check accuracy on validation set
with torch.no_grad():
    bass_outputs = dnb_lstm(drum_validation).squeeze()

    validation_count = len(drum_validation)
    validation_loss = 0
    for k in range(validation_count):
        validation_loss += reconstruction_loss(bass_outputs[k], bass_validation[k])
    print(f"#{epoch + 1} reconstruction validation loss: {validation_loss/validation_count}")
print('Finished Training')<jupyter_output>shuffle_every_epoch is on
Epoch #0
[1,     1] train loss: 221.3494110
[1,     6] train loss: 187.6525447
[1,    11] train loss: 172.0311991
[1,    16] train loss: 167.4183121
[1,    21] train loss: 154.1088537
[1,    26] train loss: 143.8433736
[1,    31] train loss: 134.0485382
[1,    36] train loss: 121.4743195
[1,    41] train loss: 88.4782550
[1,    46] train loss: 76.1715482
[1,    51] train loss: 63.7691358
[1,    56] train loss: 57.8340842
[1,    61] train loss: 63.8155594
[1,    66] train loss: 56.5195491
[1,    71] train loss: 59.0157026
[1,    76] train loss: 60.7472343
[1,    81] train loss: 55.4998086
[1,    86] train loss: 55.2104429
[1,    91] train loss: 57.7542299
[1,    96] train loss: 53.9977366
[1,   101] train loss: 56.6058102
[1,   106] train loss: 61.4204451
[1,   111] train loss: 58.8627205
[1,   116] train loss: 52.9986134
[1,   121] train loss: 53.3456720
[1,   126] train loss: 53.9071045
[1,   131] train loss: 55.1609535
[1,   136] train loss: 56.5547638
[1,  [...]<jupyter_text>Сохраним веса модели<jupyter_code>torch.save(dnb_lstm.state_dict(), "models/lstm_dnb_mono_72iter")<jupyter_output><empty_output><jupyter_text>Аналогично происходит загрузка модели:<jupyter_code>dnb_lstm.load_state_dict(torch.load("models/lstm_dnb_mono_72iter"))
batch_drum_train = drum_train[:,:,:].transpose(0,1)
batch_bass_train = bass_train[:,:].transpose(0,1)
with torch.no_grad():
    bass_outputs = dnb_lstm(batch_drum_train)
result = bass_outputs.squeeze().int()
result<jupyter_output><empty_output><jupyter_text>Попробуем сохранить результаты работы сети. На anaconda нет mido, поэтому сохраняем результаты работы просто в массивчик npy... Однако, как альтернатива, его можно поставить чере pip в conda:https://github.com/mido/mido/issues/198<jupyter_code>import mido
from decode_patterns.data_conversion import build_track, DrumMelodyPair, NumpyImage, Converter


# вспомогательная функция для генерации midi
# переводит посл-ть нот в "картинку"
def seq_to_img(bass_seq):
    bass_output = []
    for bass_note in bass_seq:
        bass_row = np.eye(1, 36, bass_note - 1)[0]
        bass_output.append(bass_row)
    bass_output = torch.tensor(bass_output).int().squeeze()
    return bass_output

converter = Converter((16,50))

# batch_drum = torch.cat((drum_train, drum_test, torch.tensor(drum_validation))).transpose(0,1)
# batch_bass = torch.cat((bass_train.int(), bass_test.int(), torch.tensor(bass_validation).int())).transpose(0,1)
def output_midi(batch_drum, batch_bass, folder, output_original=False):
    batch_drum = batch_drum.transpose(0,1)
    batch_bass = batch_bass.int().transpose(0,1)
    with torch.no_grad():
        bass_outputs = dnb_lstm(batch_drum)
        bass_outputs = bass_outputs.squeeze().int()

        for i in range(bass_outputs.size()[1]):
            def output_seq(bass_seq, prefix="sample"):
                bass_output = seq_to_img(bass_seq)

                img_dnb = torch.cat((batch_drum[:,i,:].int(),bass_output), axis=1)
                numpy_pair = NumpyImage(np.array(img_dnb), 120, 1, 1, 36)
                pair = converter.convert_numpy_image_to_pair(numpy_pair)
                mid = build_track(pair, tempo=pair.tempo)
                mid.save(f"{folder}/{prefix}{i+1}.mid")
                
            output_seq(bass_outputs[:,i])
            
            # TODO неплохо вынести генерацию оригинальной музыки в отдельный .py файл... т.е. нужно убрать этот костыль...
            if output_original:
                output_seq(batch_bass[:,i], "orig/original")<jupyter_output><empty_output><jupyter_text>Выводим обучающую и валидационную выборку<jupyter_code># # если очень надо послушать тренировчную -- лучше её перезагрузить, потому что она перемешивается
# drum, bass = data_conversion.make_lstm_dataset(height=16, patterns_file="decode_patterns/train.tsv", mono=True)
# drum = torch.tensor(drum, dtype=torch.float)
# bass = torch.tensor(bass, dtype=torch.float)
# output_midi(drum, bass, "midi/lstm_mono/train")
output_midi(drum_validation, bass_validation, "midi/lstm_mono/validation")<jupyter_output><empty_output><jupyter_text>По вкусу, выводим тот же результат для кожанных мешков на ассесмент. На самом деле ничем от валидационной выборки не отличается :)<jupyter_code>drum_hum, bass_hum = data_conversion.make_lstm_dataset(height=16, patterns_file="decode_patterns/human.tsv", mono=True)
drum_hum = torch.tensor(drum_hum, dtype=torch.float)
bass_hum = torch.tensor(bass_hum, dtype=torch.float)
output_midi(drum_hum, bass_hum, "midi/lstm_mono/human")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1091474245115453, 'fraction_numerical': 0.045115452930728245, 'mean_word_length': 3.7434709351305813, 'pattern_counts': {'":': 0, '<': 42, '<?xml version=': 0, '>': 42, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '534239', 'license': 'MIT', 'n_tokens_mistral': 4389, 'n_tokens_neox': 4232, 'n_words': 831, 'path': 'LSTM dnb mono.ipynb', 'repo_name': 'mkarpenkospb/drum_and_bass', 'size': '11260'}