Zastosowanie deep learningu do generowania różnego rodzaju danych w ostatnim czasie stało się bardzo popularne. Szybki rozwój frameworków takich jak Tensorflow czy Keras pozwala na sprawne budowanie modeli, a wykorzystanie chmur obliczeniowych zwalnia programistę z używania własnego sprzętu bądź też kupowania drogich procesorów GPU. Poniższa grafika obrazuje liczbę publikacji wydanych na przestrzeni ostatnich trzydziestu lat poświęconych generowaniu muzyki z wykorzystaniem deep learningu.

Znaczący wzrost publikacji zanotowany w ostatnich latach

Znaczący wzrost publikacji zanotowany w ostatnich latach

Istniejące projekty

Obecnie na rynku można wyróżnić sześć dużych projektów związanych z automatycznym generowaniem muzyki.

  • Magenta – jest to projekt na licencji open source stworzony przez Google. Jak sami autorzy piszą Magenta jest projektem badawczym, który bada rolę uczenia maszynowego w procesie tworzenia sztuki i muzyki. Wiąże się to z ciągłym poszukiwaniem nowych algorytmów deeplearningu. Projekt stanowi również bogate źródło inspiracji dla muzyków i artystów, ponieważ mogą oni wzbogacić swoje procesy za pomocą modeli deep learningu. Przykładem może być tutaj projekt Duet AI – program napisany w oparciu o Magentę generuje sekwencje nut na podstawie „usłyszanej sekwencji nut”. Magenta powstała na skutek prac inżynierów i badaczy z Google Brain. Niestety projekt nie posiada szczegółowej dokumentacji. Działanie programu można zobaczyć tutaj.
Logo projektu Magenta

Logo projektu Magenta

  • DeepJazz –  jest efektem pracy Ji-Sung Kima podczas 36 godzinnego hackathonu. Projekt wykorzystuje dwu warstwową sieć typu LSTM do generowania sekwencji nut na podstawie wcześniejszej nauki sekwencji z zadanego pliku MIDI. Demo można posłuchać tutaj – sztuczny Pat Metheny 🙂
Logo projektu DeepJazz

Logo projektu DeepJazz

  • BachBot – projekt badawczy stworzony przez Feynmana Lianga na uniwersytecie w Cambridge. Projekt na wejściu przyjmuje pliki MIDI, dokładnie są to chorały JS Bacha.
Logo projektu BachBot

Logo projektu BachBot

  • FlowMachines – projekt badawczy, którego celem jest badanie i rozwijanie systemów sztucznej inteligencji zdolnych do generowania muzyki samodzielnie lub we współpracy z ludźmi. W roku 2017 dzięki FlowMachines udało się wygenerować pełny utwór muzyczny gatunku pop.  Projekt został sfinansowany przez Europejską radę ds. Badań Naukowych w ramach 70 programu ramowego UE. Badania zostały zapoczątkowane przez francuskiego naukowca Francoisa Pachet w Sony Computer Science Laboratories (Sony SCL Paris) oraz Uniwersytet Piotra i Marii Curie (UPMC).
Logo projektu FlowMachines

Logo projektu FlowMachines

  • WaveNet –  pobiera surowy sygnał audio i syntezuje próbkę wyjściową na podstawie próbki wejściowej. Projekt nie jest oparty o wolny kod źródłowy, ale doczekał się implementacji przez społeczność. Dzięki temu, że sieć używa surowego pliku audio jako wejścia, może generować dowolny instrument. Wykorzystane algorytmy w projekcie są bardzo kosztowne obliczeniowa. Potrzeba kliku minut treningu po to aby wygenerować sekundę dźwięku. Jeden z badaczy pracujący dla Google Sageev Oore z projektu Magenta opisał na swoim blogu jakie wnioski może wyciągnąć kompozytor z wygenerowanego materiału z sieci WaveNet

W ostatnim czasie na rynku pojawiło się też kilka startupów (JukedeckAivaAmper), które oferują generowanie muzyki na żądanie. Od użytkownika nie wymaga się specjalistycznej wiedzy, wystarczy że za pomocą suwaków dostosuje wybrany przez siebie styl wygenerowanego utworu, który może wykorzystać np. w reklamie czy jako podkład pod podcast.

Rekurencyjne sieci neuronowe

Rekurencyjne sieci neuronowe (ang. recurrent neural networks, RNN)(Rumenlhart 1986) to rodzina sieci neuronowych służąca do pracy z danymi sekwencyjnymi. Sieci rekurencyjne mają zastosowanie w tłumaczeniu tekstu, rozpoznawaniu obrazu, rozpoznawaniu mowy czy generowaniu muzyki, ponieważ wszystkie te mechanizmy potrzebują do działania sekwencji. Jedną z pierwszych koncepcji systemów uczących się i modeli statystycznych była możliwość współdzielenia parametrów w różnych częściach modelu. Takie podejście pozwala na rozszerzenie modelu i zastosowanie go do przykładów o różnych postaciach. Istnieje klika różnych kategorii przetwarzania danych sekwencyjnych, które można podzielić ze względu na dane wejściowe i wyjściowe. Takiego podziału dokonał Andrej Karpathy, który opisał w artykule The Unreasonable Effectiveness of Recurrent Neural Networks. Rysunek poniżej obrazuje różne zależności między danymi wejściowymi, a wyjściowymi.

W przypadku kiedy dane wejściowe zawierają sekwencje istnieje klika różnych przypadków modeli, które to zwrócą różne wyjścia. Można je podzielić na trzy główne kategorie.

  • Wiele do jednego – Dane wejściowe to sekwencja, wyjście to wektor o stałym rozmiarze. Dla przykładu na wejściu jest fragment tekstu, a na wyjściu jedno słowo np. emocja opisująca fragment tekstu, inny przykład to sekwencja nut na wejściu, a na wyjściu nazwa gatunku muzycznego pasującego do zadanej sekwencji.
  • Jeden do wielu – Dane wejściowe nie są sekwencją, natomiast dane wyjściowe składają się z sekwencji. Przykład to opisywanie obrazów, wejście to obraz, a na wyjściu opis obrazu.
  • Wiele do wielu – Dane wejściowe i wyjściowe składają się z sekwencji. Przykład to tłumaczenie jednego języka na inny, generowanie nowego utworu muzycznego na podstawie innego.

Innym historycznym przykładem sieci rekurencyjnej jest sieci Elmana (Elman, 1990) – sieć złożona z trzech podstawowych warstw (wejściowej, ukrytej, wyjściowej) oraz dodatkowej warstwy, której połączenia wejściowe są połączeniami wejściowymi warstwy ukrytej. Sieci Jordana posiadają podobną strukturę do sieci Elmana, połączenia wejściowe do warstwy kontekstowej są połączeniami wyjściowymi z warstwy wyjściowej. Sieci Hopfielda nie posiadają wyróżnionych warstw, każdy neuron połączony jest ze wszystkimi neuronami w sieci, poza sobą, neurony nie mają pętli, każda para neuronów ma połączenie symetryczne.

Rekurencyjną sieć neuronową można przedstawić za pomocą poniższego wzoru, który oblicza sekwencje wektorów $x$ stosując zasadę rekurencji dla każdego kroku czasowego:

(1)    \begin{equation*} h_{t} = f_{W} ( h_{t-1}, x_{t} ) \end{equation*}

  \begin{itemize} \item $h_{t}$ - nowy stan \item $f_{W}$ - funkcja aktywacji z parametrem $W$ \item $h_{t-1}$ - poprzedni stan \item $x_{t}$ - wektor wejścia w kroku czasowym $t$ \end{itemize}

Powyższy wzór można przedstawić za pomocą schematu:

Schemat rekurencyjnej sieci neuronowej z jedną warstwą ukrytą

Schemat rekurencyjnej sieci neuronowej z jedną warstwą ukrytą

Model sieci rekurencyjnej można rozwinąć w czasie, wtedy otrzymamy:

Rekurencyjna sieć neuronowa rozwinięta w czasie

Rekurencyjna sieć neuronowa rozwinięta w czasie

Sekwencja wejściowa $x = (x^{1}, x^{2}, ..., x^{T_{x}})$ jest aplikowana do sieci w $T_{x}$ krokach czasowych. Na wyjściu sieć zwraca sekwencję $y = (y^{1}, y^{2}, ..., y^{T_{x}})$.

Rekurencyjną sieć neuronową można postrzegać jako powtarzanie pojedynczej komórki. Poniższy rysunek opisuje operacje dla pojedynczego kroku czasowego komórki w rekurencyjnej sieci neuronowej.

Podstawowa komórka w rekurencyjnej sieci neuronowej

Podstawowa komórka w rekurencyjnej sieci neuronowej

Powyższa komórka na wejściu otrzymuje $x^{t}$ oraz $a^{t-1}$ z poprzedniego ukrytego stanu, na wyjściu komórka zwraca $a^{t}$, wyjście będzie wykorzystane jako wejście do następnej komórki oraz zostanie użyte do predykcji \hat{y}^{t}.

Każda krawędź skojarzona jest z macierzą wag. Macierz wag jest niezależna od danego kroku czasowego $t$. Macierze wag, które występują na rysunku powyżej:

  \begin{itemize} \item $\textbf{\textit{W}}_{ax}$ - macierz wag pomnożona przez wektor wejściowy \item $\textbf{\textit{W}}_{aa}$ - macierz wag pomnożona przez stan ukryty \item $\textbf{\textit{W}}_{ya}$ - macierz wag pomiędzy warstwą ukrytą, a warstwą wyjściową \end{itemize}

Trening rekurencyjnej sieci neuronowej odbywa się z wykorzystaniem algorytmu Backpropagation Through Time (Backpropagation Through Time: What It Does and How to Do It (Paul Werbos, Proceedings of IEEE, 78(10):1550-1560, 1990)). Algorytm bazuje na metodzie spadających gradientów.

Sieci rekurencyjne stosuje się aby zachowywać długotrwałe zależności w czasie. Jednak niesie to ze sobą pewne problemy, ponieważ im więcej danych nagromadzonych w poszczególnych krokach czasowych to istnieje duże ryzyko, że gradienty obliczane z wykorzystaniem algorytmu BPTT będą się gromadzić. Takie zjawisko może doprowadzić do problemu zanikającego gradientu lub jego eksplozji. Problem ten został dokładnie opisany przez R. Pascanu, T. Mikolov, i Y. Bengio w dziele On the difficulty of training recurrent neural networks.

Na przestrzeni lat zostały opracowane dwa sposoby, które rozwiązują ten problem:

  • Truncated backpropagation through time (TBPTT)
  • Long short-term memory (LSTM)

Long Short-Term Memory

Współcześnie najbardziej efektywne modele sekwencyjne, które stosowane są w praktyce nazywane są bramkowymi sieciami RNN. Do tych modeli zaliczana jest architektura długiej pamięci krótkoterminowej (ang. long short-term memory). Pierwsza koncepcja architektury LSTM została przedstawiona przez dwóch niemieckich naukowców S. Hochreiter’a oraz J. Schmidhuber’a w 1997 (Sepp Hochreiter, Jurgen Schmidhuber, Long Short-Term Memory, Neural Computation). Głównym blokiem konstrukcyjnym LSTM jest komórka pamięci, która reprezentowana jest przez ukrytą warstwę w rekurencyjnej sieci neuronowej. Sieci rekurencyjne z blokiem LSTM nazywamy sieciami LSTM. Nad poprawieniem pierwotnej koncepcji LSTM pracowało wiele osób przez kilka lat, wyniki poprawek zostały opisane w pracy In addition to the original authors, a lot of people contributed to the modern LSTM.  Sieci LSTM zostały zaprojektowane w celu uniknięcia problemu długotrwałej zależności. Trening sieci LSTM pozwala na zapamiętanie długotrwałych zależności pomiędzy danymi wejściowymi, a wyjściowymi wynikami zwracanymi przez sieć. Struktura komórki LSTM może przypominać pamięć komputera, ponieważ może odczytywać, zapisywać i usuwać informacje za pomocą odpowiednich bramek. Komórka decyduje, czy przechować lub usunąć informacje w zależności od przypisanej wagi do informacji. Wagi dobierane są w procesie uczenia, z upływem czasu komórka LSTM dowiaduje się, które informacje są ważne, a które nie. Struktura komórki LSTM została przedstawiona na rysunku poniżej.

Komórka LSTM

Komórka LSTM

Głównym rdzeniem komórki LSTM jest wyprowadzenie, które na wejściu przyjmuje pamięć z poprzedniego kroku czasowego $c^{\langle t-1 \rangle}$ i zwraca aktualny stan pamięci $c^{\langle t \rangle}$. Komórka posiada trzy wejścia. Pierwsze z nich to wektor $x^{t}$ w kroku czasowym $t$. $a^{\langle t-1 \rangle}$ oznacza wyjście z poprzedniej komórki LSTM. Wcześniej wspomniane $c^{\langle t-1 \rangle}$ to pamięć z poprzedniej jednostki. Symbol $\odot$ zamieszczony na schemacie to iloczyn Hadamarda.

Komórka LSTM posiada trzy różne typy bramek:

  • Bramka zapomnienia (ang. forget gate) pozwala komórce pamięci na wymazanie pamięci. Wektory wejściowe po wymnożeniu przez wagi trafiają do sigmoidalnej funkcji aktywacji. Funkcja aktywacji zwraca wektor, którego elementy są z zakresu (0, 1). Kolejno wektor jest poddany iloczynowi Hadamarda ze wcześniejszym stanem komórki pamięci $c^{\langle t-1 \rangle}$. Kluczową rolę pełni wektor zwrócony przez funkcję aktywacji, ponieważ jeżeli jego elementy są bliskie zera to po zastosowaniu iloczynu Hadamarda z poprzednim stanem pamięci spowoduje wymazanie odpowiednich elementów z wektora $c^{\langle t-1 \rangle}$. W przeciwnym wypadku jeżeli wartości wektora z funkcji aktywacji będą bliskie zeru to elementy z wektora $c^{\langle t-1 \rangle}$ zostaną zachowane. Bramka zapomnienia nie była częścią oryginalnej komórki LSTM, została dodana kilka lat później.
  • Bramka aktualizacji (ang. update gate) jej zadaniem jest aktualizacja stanu komórki. Podobnie jak w przypadku bramki zapomnienia bramka aktualizacji zwraca wektor z wartościami z przedziału (0, 1). Bramka jest powiązana z funkcją aktywacji jaką jest tangens hiperboliczny. Zadaniem funkcji aktywacji jest obliczenie nowych wartości, które mogą zostać wstawione do stanu komórki. Funkcja aktywacji tanh przyjmuje wartości z przedziału (-1, 1) więc otrzymany wektor może zawierać elementy o wartościach ujemnych. Iloczyn Hadmarda dwóch wektorów dodawany jest do stanu pamięci. W wyniku otrzymywany jest wektor $c^{t}$ – zaktualizowany stan pamięci.
  • Bramka wyjścia (ang. output gate) – ostatni element komórki LSTM, który składa się z dwóch komponentów: funkcje aktywacji tanh oraz wyjściową funkcję sigmoidalną.

W pierwszym kroku przepływu danych przez komórkę LSTM dane muszą przejść przez bramkę zapomnienia. W tym kroku bramka decyduje, które informacje mają zostać zapominanie, a jakie zachować. Bramka przyjmuje sekwencję danych $x^{t}$ oraz wektor wyjściowy z poprzedniej komórki $h^{t-1}$. Schemat przepływu został zilustrowany poniżej.

Pierwszy krok przepływu danych

Pierwszy krok przepływu danych

W kolejnym kroku następna bramka decyduje jaką informacje należy zachować. Bramka składa się z dwóch części: funkcji sigmoidalej oraz tanh. W wyniku otrzymywany jest wektor, który jest nazywany wektorem nowych wartości kandydujących.

Drugi krok przepływu informacji przez komórkę LSTM

W kolejnym kroku następuje aktualizacja starego stanu komórki, który jest określony przez $c^{t-1}$. Zaktualizowany wektor $c^{t}$ jest odbierany przez kolejną komórkę LSTM w kolejnym kroku czasowym.

Trzeci krok przepływu informacji przez komórkę LSTM

Trzeci krok przepływu informacji przez komórkę LSTM

Ostatni krok do zwrócenie wartości $h^{t}$, wartość $h^{t}$ jest wartością przewidywaną przez komórkę LSTM w czasie $t$. Połączenie skierowane do góry może posłużyć jako wejście do kolejnej warstwy ukrytej, znajdującej się bezpośrednio nad aktualną. Połączenie skierowane w prawo, stanowi wejście do następnej komórki LSTM.

Czwarty, ostatni krok przepływu przez komórkę LSTM

Czwarty, ostatni krok przepływu przez komórkę LSTM

Badania wykazują, że sieci LSTM znacznie szybkiej przychodzi poznawanie długotrwałych zależności, przede wszystkim na sztucznych zbiorach danych, zaprojektowanych do testowania takich możliwości (Bengio et al., 1994; Hochreiter and Schmidhuber, 1997; Hochreiter et al., 2001). Testy odbywają się na specjalnych wymagających zadaniach przetwarzania sekwencji (Graves, 2012 Graves et al., 2013; Sutskever et al., 2014). Nie wszystkie warianty komórek LSTM są takie same jak opisane powyżej. W publikacjach opisujących LSTM można znaleźć wiele innych wariantów, które lekko odbiegają od oryginalnego modelu. Jednym z popularnych odmian LSTM jest dodatek wprowadzony przez Gersa i Schmidhubera (2000), który wzbogaca standardową komórkę LSTM o tzw. peephole connections. Są to dodatkowe połączenia pomiędzy stanem pamięci, a bramkami. Nieco innym wariantem LSTM jest Gated Recurrent Unit (GRU), koncept ten został wprowadzony przez Cho, et al (2014). Jednostka GRU łączy bramkę zapomnienia oraz bramkę aktualizacji. Połączony jest także stan komórki ze stanem ukrytym. Uzyskany model jest prostszy niż standardowe modele LSTM i jest coraz bardziej popularny. Inne popularne modele to Depth Gated RNNs authorstwa Yao, et al (2015). Zupełnie inne podejście przedstawił Koutnik w 2014 w pracy Clockwork RNN. Greff, et al. (2015) zrobił porównanie wszystkich popularnych wariantów i stwierdził, że wszystkie działają tak samo. Rafał Józefowicz przetestował ponad dziesięć tysięcy różnych architektur i wskazał, że niektóre z nich działają lepiej od standardowej komórki LSTM w zależności od postawionego problemu. W następnym wpisie opisze w jaki sposób wytrenować sieć LSTM, jako dane posłużą pliki MIDI. W efekcie sieć będzie wstanie generować nowe nuty na podstawie nauczonych sekwencji.

Jeżeli temat Cię zainteresował to zostaw komentarz!

Część 1 – algorytmiczne generowanie muzyki 

Część 2 – Ukryte Modele Markowa 

Polecam inne artykuły z kategorii informatyka:

Uruchomienie Linuxa obok androida

Steganografia z poziomu teoretycznego i programowalnego

 

Sieci neuronowe generują muzykę – część 3 cyklu
4.2 (84%) 5 votes
Share: