С развитием ИИ не шутка, что машина сможет воспроизводить музыку. Я не уверен, что музыка будет иметь такие эмоции и сострадание, которые можно было бы легко заменить человеком. В любом случае, в этом блоге я покажу вам, как мы можем использовать глубокое обучение для создания музыки. Конечно, вам нужно сначала научить их, чтобы они работали сами по себе.

Сначала нам нужно некоторое понимание базовой структуры музыки. Если мы возьмем в качестве примера фортепиано, то первое, что заметит даже самый любительский музыкант, — это набор клавиш — одни черные, другие белые — которые при ударе издают разные звуки или ноты. Комбинация этих нот, известная как аккорд на фортепиано, представляет собой как минимум 3 ноты, сыгранные вместе, а в некоторых случаях и больше. Песни написаны в тональностях и так называемой ключевой подписи, чтобы вы могли определить основную ноту для начала. Например, аккорд до мажор — это основная нота до (тоника аккорда), третья интервальная нота ми и пятая интервальная нота соль.

Теперь, чтобы научить машину играть музыку, нам нужно, чтобы эти музыкальные ноты были введены в ее мозг. Как получить эти записи??? Здесь мы собираемся использовать музыку, загруженную в формате MIDI. MIDI – это стандарт связи, позволяющий цифровому музыкальному оборудованию говорить на одном языке. MIDI — это сокращение от «Цифровой интерфейс музыкальных инструментов». Это протокол, который позволяет компьютерам, музыкальным инструментам и другому оборудованию обмениваться данными.

Вот несколько примеров фортепианной музыки, которые мы собираемся использовать для обучения: https://github.com/SciSharp/Keras.NET/tree/master/Examples/MusicGeneration/PianoDataset

Мы собираемся разделить код cmplete на 3 части:

  1. Подготовка данных
  2. Обучение модели
  3. Музыкальное поколение

Начните с создания нового консольного проекта в Visual Studio. Добавьте в проект следующие пакеты nuget:

  1. Keras.NET: https://www.nuget.org/packages/Keras.NET
  2. Melanchall.DryWetMidi: https://www.nuget.org/packages/Melanchall.DryWetMidi

Keras будет использоваться для построения модели глубокого обучения для обучения и прогнозирования следующей заметки. В то время как Melanchall.DryWetMidi для подготовки данных и воспроизведения сгенерированных нот. Загрузите все миди-файлы и поместите их в папку PianoDataset внутри проекта. Создайте новый класс C# «WavenetMusicGeneration.cs».

Подготовка данных

Откройте «WavenetMusicGeneration.cs» и добавьте следующие поля.

public class WavenetMusicGeneration
 {
        static int no_of_timesteps = 32; //No. of notes to teach per step
        static NDarray train_x = null; // Training notes set
        static NDarray train_y = null; // Next note to play 
        static int output = 127; //Max number of notes a piano have
 }

Нам нужна функция для чтения MIDI-файла и получения списка нот.

private static Note[] ReadMidi(string file)
 {
            Console.WriteLine("Loading music file: " + file);
            var mf = MidiFile.Read(file);
            return mf.GetNotes().ToArray();
}

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

public static void PrepData()
{
            //notes array list declaration
            List<Note[]> notes_array = new List<Note[]>();

            //Get all the file list
            var files = Directory.GetFiles("./PianoDataset");

            //Loop through the files and read the notes, put them in the array
            foreach (var file in files)
            {
                notes_array.Add(ReadMidi(file));
            }

            //Show first 15 notes for the first music file
             Console.WriteLine("Displaying first 15 notes of first music");
            foreach (var n in notes_array[0].Take(15))
            {
                Console.Write(n.ToString() + " ");
            }

            //Declare X and Y which will hold the training set
            List<float> x = new List<float>();
            List<float> y = new List<float>();
           
            //Loop through the notes and prepare X and Y set.
            foreach (var notes in notes_array)
            {
                for (int i = 0; i < notes.Length - no_of_timesteps; i++)
                {
                    var input = notes.Skip(i).Take(no_of_timesteps).Select(x => Convert.ToSingle(x.NoteNumber)).ToArray();
                    var output = Convert.ToSingle(notes[i + no_of_timesteps].NoteNumber);
                    x.AddRange(input);
                    y.Add(output);
                }
            }

            // Finally convert them to numpy array format for neural network training.
            train_x = np.array(x.ToArray(), dtype: np.float32).reshape(-1, 32);
            train_y = np.array(y.ToArray(), dtype: np.float32);
}

Вывод подготовительной функции:

Научите модель

Теперь наступает настоящая часть, чтобы построить небольшой участок мозга и научить их музыке. Мы собираемся использовать вариацию (более простую версию) архитектуры WaveNet. WaveNet — это глубокая нейронная сеть для генерации необработанного звука. Он был создан исследователями из лондонской компании DeepMind, специализирующейся на искусственном интеллекте. Мы собираемся повторно использовать архитектуру для создания музыки :). Вы можете поискать в Google дополнительную информацию о WaveNet, прежде чем двигаться дальше.

Вот код, который построит модель и обучит ее, используя заранее подготовленный набор данных.

public static void BuildAndTrain()
{
            //Model to hold the neural network architecture which in this case is WaveNet
            var model = new Sequential();
            // Starts with embedding layer
            model.Add(new Embedding(output, 100, input_length: 32));

            model.Add(new Conv1D(64, 3, padding: "causal", activation: "tanh"));
            model.Add(new Dropout(0.2));
            model.Add(new MaxPooling1D(2));

            model.Add(new Conv1D(128, 3, activation: "relu", dilation_rate: 2, padding: "causal"));
            model.Add(new Dropout(0.2));
            model.Add(new MaxPooling1D(2));

            model.Add(new Conv1D(256, 3, activation: "relu", dilation_rate: 4, padding: "causal"));
            model.Add(new Dropout(0.2));
            model.Add(new MaxPooling1D(2));

            //model.Add(new Conv1D(256, 5, activation: "relu"));
            model.Add(new GlobalMaxPooling1D());

            model.Add(new Dense(256, activation: "relu"));
            model.Add(new Dense(output, activation: "softmax"));

            // Compile with Adam optimizer
            model.Compile(loss: "sparse_categorical_crossentropy", optimizer: new Adam());
            model.Summary();

            // Callback to store the best trained model
            var mc = new ModelCheckpoint("best_model.h5", monitor: "val_loss", mode: "min", save_best_only: true, verbose: 1);

            //Method to actually train the model for 100 iteration
            var history = model.Fit(train_x, train_y, batch_size: 32, epochs: 100, validation_split: 0.25f, verbose: 1, callbacks: new Callback[] { mc });

            // Save the final trained model which we are going to use for prediction
            model.Save("last_epoch.h5");
}

Процесс обучения немного трудоемкий, но если вы дали GPU, то он ускорится в 10 раз. Результаты обучения, как показано ниже:

Создание новой музыки

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

public static List<int> GenerateNewMusic(int n = 20)
{
            //Load the trained model
            var model = Model.LoadModel("last_epoch.h5");
            //Get a random 32 notes from the train set which we will use to get new notes
            var ind = np.random.randint(0, train_x.shape[0]);
            var random_music  = train_x[ind];

            //Build the prediction variable with sample 32 notes
            List<float> predictions = new List<float>();
            predictions.AddRange(random_music.GetData<float>());

            //Loop through N times which means N new notes, by default its 20
            for (int i = 0; i < n; i++)
            {
                // Reshape to model adaptaed shape
                random_music = random_music.reshape(1, no_of_timesteps);
                //Predict the next best note to be played
                var prob = model.Predict(random_music)[0];
                var y_pred = np.argmax(prob, axis: 0);

                //Add the prediction and pick the last 32 to predict the next music note
                predictions.Add(y_pred.asscalar<float>());
                random_music = np.array(predictions.Skip(i + 1).ToArray());
            }

            //Finally skip the first 32 sample notes and return the rest N new predicted notes.
            return predictions.Skip(no_of_timesteps - 1).Select(x=>(int)x).ToList();
}

Функция для воспроизведения этих нот. Мы используем устройство воспроизведения по умолчанию на компьютере и отправляем MidiEvent, который будет воспроизводить звук фортепиано.

private static void PlayNotes(List<int> notes)
{
            List<List<MidiEvent>> musicNotes = new List<List<MidiEvent>>();
            var playbackDevice = OutputDevice.GetAll().FirstOrDefault();
            foreach (var note in notes)
            {
                Note n = new Note(SevenBitNumber.Parse(note.ToString()));
                Console.Write(n + " ");
                playbackDevice.SendEvent(new NoteOffEvent(SevenBitNumber.Parse(note.ToString()), SevenBitNumber.MaxValue));
            }
}

Последняя основная функция, которая вызывает все эти вышеперечисленные функции:

static void Main(string[] args)
{
            WavenetMusicGeneration.PrepData();
            WavenetMusicGeneration.BuildAndTrain();
            var notes = WavenetMusicGeneration.GenerateNewMusic(20);
            Console.WriteLine("\n\nPlaying auto generated music....\n");
            PlayNotes(notes);
}

Потрясающе, правда? Но ваше обучение на этом не заканчивается. Существует множество способов еще больше улучшить производительность модели:

  • Мы можем настроить предварительно обученную модель для создания надежной системы.
  • Соберите как можно больше обучающих данных, поскольку модель глубокого обучения хорошо обобщает большие наборы данных.

Пример кода: https://github.com/SciSharp/Keras.NET/tree/master/Examples/MusicGeneration

Первоначально опубликовано на https://www.tech-quantum.com 3 мая 2020 г.