Музыкальный ИИ: создайте нейронную сеть, которая предсказывает, будет ли песня иметь успех мирового уровня. (с кодом Python)

Нейронные сети оказались настолько выдающимися благодаря своей способности находить закономерности и обобщать. В этом уроке я покажу вам пошаговую инструкцию с кодом Python для создания нейронной сети, способной предсказать, станет ли песня хитом мирового уровня. Все это основано на данных 1000 лучших и худших песен всех времен.

Примечание. Вам необходимо загрузить данные самостоятельно. В предоставленном коде я показал пример из 14 песен, но вы можете легко расширить его даже до миллионов песен. Просто имейте в виду, что вам нужно МНОГО памяти для обучения :)

Примечание 2: ссылка на github с полным проектом

Начнем!

Набор данных:

Что касается музыки, то у каждого свой вкус — поэтому, чтобы быть максимально объективным, я буду использовать рейтинги лучших и худших песен за все время со следующих сайтов:

Теперь для построения модели нам необходимо:

  • Загружайте данные в формате наилучшего качества
  • Разделить данные на обучающие/тестовые срезы
  • Создайте модель и обучите ее
  • Проанализируйте результаты и спланируйте следующие шаги для тонкой настройки модели.

Загрузить данные

Для простоты я решил показать вам пример загрузки аудио с YouTube. В питоне это очень просто. Для этой цели разработан очень простой пакет Python youtube_dl:

with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    ydl.download([link])

Примечание: чтобы иметь возможность иметь формат wav, вам необходимо установить LIBAV из FFMPEG. Все инструкции приведены на сайте youtube_dl и в документации. (вспомогательная ссылка)

Здесь у нас есть полный код для загрузки данных:

SAVE_PATH = r'your\path\to\the\project\data'


def download(list, subfolder):
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'wav',
            'preferredquality': '192',
        }],
        'outtmpl': SAVE_PATH + subfolder + '/%(title)s.%(ext)s',
    }

    for i, link in enumerate(list):

        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            ydl.download([link])

# your_list - list of youtube links you want to download
download(your_list, r'/songs')

Разделить данные

Для разделения датасета на train/test рекомендую использовать модуль scikit-learn.

Примечание. Используя пример кода из этой статьи, вам не нужно разбивать наборы данных, так как они уже разделены вручную.

Создать загрузчик данных для модели PyTorch

PyTorch упрощает создание нейронной сети на основе пользовательского набора данных. Они придумали обертку, которая экономит массу времени. В вашем пользовательском наборе данных должно быть только два магических метода: __getitem__ и __len__.

  • __getitem__ возвращает кортеж пары (x, y).
  • __len__ возвращает размер данных.

Вот наш код для загрузки песен с локального диска в формате wav:

class Dataset(torch.utils.data.Dataset):

    def __init__(self, list_IDs, labels, subfolder):
        self.labels = labels
        self.list_IDs = list_IDs
        self.subfolder = subfolder

    def __len__(self):
        return len(self.list_IDs)

    def __getitem__(self, index):
        ID = self.list_IDs[index]
        samplerate, X = wavfile.read(os.getcwd() + '\\data\\' + self.subfolder + str(ID) + '.wav')
        y = self.labels[ID]
        X = np.array(X[100000:])
        if len(X.shape) > 1:
            X = [sum(i) for i in X]
        X = np.trim_zeros(X, 'f')

        return torch.as_tensor(torch.from_numpy(np.array(X[:DATA_POINTS])), dtype=torch.float), y

И это все, что нам нужно.

Дизайн модели

Архитектура модели, представленная ниже, очень проста и фактически работала в качестве базовой модели. В следующих уроках мы будем использовать более продвинутые архитектуры ;)

class MusicNet(nn.Module):
    def __init__(self):
        super(MusicNet, self).__init__()
        self.linn1 = nn.Linear(DATA_POINTS, 5000)
        self.linn11 = nn.Linear(5000, 5000)
        self.linn2 = nn.Linear(5000, 1000)
        self.linn22 = nn.Linear(1000, 1000)
        self.linn3 = nn.Linear(1000, 100)
        self.linn4 = nn.Linear(100, 2)

    def forward(self, x):
        x = self.linn1(x)
        x = F.relu(x)
        x = self.linn11(x)
        x = F.relu(x)
        x = self.linn2(x)
        x = F.relu(x)
        x = self.linn22(x)
        x = F.relu(x)
        x = self.linn3(x)
        x = F.relu(x)
        x = self.linn4(x)
        output = F.log_softmax(x, dim=1)
        return output

Обучение модели:

Для обучения нейронной сети pytorch нам нужны параметры:

  • model — объект дизайна нашей модели из предыдущего раздела в виде:
model = MusicNet()
  • устройство — cpu (медленно) или cuda (быстро): cuda доступно, только если у вас есть графическая карта NVIDIA RTX
use_cuda = torch.cuda.is_available()
device = torch.device(“cuda” if use_cuda else “cpu”)
print(“Running on “, device)
  • train_loader — смотрите в разделе загрузчик данных
  • оптимизатор — смотрите здесь все оптимизаторы
  • эпоха — количество итераций, которое мы установили для обучения модели

Вот полный метод обучения модели:

def train(args, model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)

        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % args.log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))
            if args.dry_run:
                break

Результаты

Протестируем модель. Нужен дополнительный метод для проверки предсказанных моделью меток невиданных (точнее, не слышанных ранее) песен.

Вот полный код для этого:

def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))

На 1000 обученных песнях, представленных выборкой всего 5 секунд (большее количество входных данных приводило к проблеме с нехваткой памяти) каждая и 100 песен, использованных для тестирования модели точность составила 73,64%.

Примечание. Следующая часть будет опубликована, как только я куплю больше оперативной памяти (похоже, мне нужно 128 ГБ оперативной памяти) :D

К сожалению, 32 ГБ недостаточно, так как на моем личном ПК закончилась память, когда ввод песен длился более 5 секунд.

Спасибо и хороших выходных!