Из этой статьи вы узнаете о модели цепи Маркова и о том, как ее можно применить для создания музыки.

Что такое цепь Маркова?

Цепь Маркова - это модель, описывающая последовательность возможных событий. Эта последовательность должна удовлетворять предположению Маркова - вероятность следующего состояния зависит от предыдущего состояния, а не от всех предыдущих состояний в последовательности.

Это может звучать как упрощение реальных случаев. Например, чтобы применить цепь Маркова для прогноза погоды, нам нужно сделать предположение, что погода завтра зависит только от текущей погоды, и сделать вид, что нет никаких других факторов, таких как время года и т. Д.

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

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

Создание музыки с помощью цепи Маркова

Есть много замечательных статей и сообщений в блогах, объясняющих цепи Маркова. Так что, не вдаваясь в теоретические подробности, применим эту модель на практике! Наиболее популярное применение цепи Маркова - это язык и речь, например, предсказание следующего слова в предложении. Но что, если мы попытаемся создать музыку?

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

Хорошо, вот план:

  1. Возьмите корпус аккордов
  2. Рассчитайте распределение вероятностей для аккордов, следующих за конкретным аккордом
  3. Определите первый аккорд или сделайте случайный выбор
  4. Произвести случайный выбор следующего аккорда с учетом распределения вероятностей
  5. Повторите шаги 4 для созданного аккорда.
  6. Стохастическая музыка потрясающая!

Пошаговое руководство:

В качестве источника данных я подготовил файл CSV с последовательностью аккордов, взятой у одной известной группы из Ливерпуля. Вы можете найти этот файл на GitHub.

Пример последовательности:

['F', 'Em7', 'A7', 'Dm', 'Dm7', 'Bb', 'C7', 'F', 'C', 'Dm7',...]

Сначала делаем биграммы:

['F Em7', 'Em7 A7', 'A7 Dm', 'Dm Dm7', 'Dm7 Bb', 'Bb C7', ...]

Теперь, если я возьму аккорд F как начальный аккорд в последовательности, какова будет вероятность, что за ним последуют другие аккорды?

Есть 18 биграмм, которые начинаются с аккорда F:

['F Em7', 'F C', 'F F', 'F Em7', 'F C', 'F A7sus4', 'F A7sus4', ...]

Затем мы вычислим частоту появления каждой уникальной биграммы в последовательности:

{'F Em7': 4, 'F C': 4, 'F F': 3, 'F A7sus4': 4, 'F Fsus4': 2, 'F G7': 1}

Если нормализовать его, мы получим вероятности:

{'F Em7': 0.222,
 'F C': 0.222,
 'F F': 0.167,
 'F A7sus4': 0.222,
 'F Fsus4': 0.111,
 'F G7': 0.056}

Часто это можно интерпретировать в виде графика:

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

Итак, цепь Маркова - это случайный процесс, или вы предпочитаете случайный процесс. Чтобы перейти к следующему состоянию, мы будем выбирать аккорд случайным образом, но в соответствии с распределением вероятностей, в нашем случае, это означает, что мы с большей вероятностью выберем аккорд C, чем G7.

Для данного аккорда F у нас есть 6 кандидатов на следующий аккорд:

options
>>> ['Em7', 'C', 'F', 'A7sus4', 'Fsus4', 'G7']

И у каждого аккорда соответственно свои вероятности:

probabilities
>>> [0.222, 0.222, 0.167, 0.222, 0.111, 0.056]

Numpy, начиная с версии 1.7.0, может выполнять случайную выборку в соответствии с заданным распределением вероятностей, давайте воспользуемся этим:

import numpy as np
choise = np.random.choice(options, p=probabilities)

Допустим, наш результат этого случайного выбора был Em7. Теперь у нас есть новое состояние, и мы можем повторить весь процесс снова.

Общий рабочий процесс выглядит так:

# Our current state
chord = 'F'
# create list of bigrams which stats with current chord
bigrams_with_current_chord = [bigram for bigram in bigrams if bigram.split(' ')[0]==chord]
# count appearance of each bigram
count_appearance = dict(Counter(bigrams_with_current_chord))
# convert apperance into probabilities
for ngram in count_appearance.keys():
  count_appearance[ngram] = count_appearance[ngram]/len(bigrams_with_current_chord)
# create list of possible options for the next chord
options = [key.split(' ')[1] for key in count_appearance.keys()]
# create  list of probability distribution
probabilities = list(count_appearance.values())
# Make random prediction
np.random.choice(options, p=probabilities)

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

np.random.seed(42)

Мы можем свести весь процесс к двум функциям:

def predict_next_state(chord:str, data:list=bigrams):
    """Predict next chord based on current state."""
    # create list of bigrams which stats with current chord
    bigrams_with_current_chord = [bigram for bigram in bigrams if bigram.split(' ')[0]==chord]
    # count appearance of each bigram
    count_appearance = dict(Counter(bigrams_with_current_chord))
    # convert apperance into probabilities
    for ngram in count_appearance.keys():
        count_appearance[ngram] = count_appearance[ngram]/len(bigrams_with_current_chord)
    # create list of possible options for the next chord
    options = [key.split(' ')[1] for key in count_appearance.keys()]
    # create  list of probability distribution
    probabilities = list(count_appearance.values())
    # return random prediction
    return np.random.choice(options, p=probabilities)
def generate_sequence(chord:str=None, data:list=bigrams, length:int=30):
    """Generate sequence of defined length."""
    # create list to store future chords
    chords = []
    for n in range(length):
        # append next chord for the list
        chords.append(predict_next_state(chord, bigrams))
        # use last chord in sequence to predict next chord
        chord = chords[-1]
    return chords

Теперь мы можем сгенерировать последовательность нужной длины:

generate_sequence('C')

Пример последовательности:

['Bb',
 'Dm',
 'C',
 'Bb',
 'C7',
 'F',
 'Em7',
 'A7',
 'Dm',
 'Dm7',
 'Bb',
 'Dm',
 'Gm6'
 ...
 ]

Я пробовал сыграть ее на гитаре, и она действительно звучит как песня, которую может написать группа из Ливерпуля. Единственное, чего не хватает, это текста, но мы можем использовать ту же модель, обученную на корпусе текста, для генерации текста для песни :).

Резюме

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

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

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

Код и набор данных можно найти в моем репозитории GitHub.

Использованная литература:

  1. Евгений Сенета. Марков и создание цепей Маркова (2006) Исходный документ в формате PDF
  2. Хейс, Брайан. Первые звенья в цепи Маркова. American Scientist 101.2 (2013): 252. Исходный документ в формате PDF