Want To Generate Your Own Music Using Deep Learning? Here's A Guide To Do Just That!
Want To Generate Your Own Music Using Deep Learning? Here's A Guide To Do Just That!
Here's a
Guide to do just that!
A D VA NC E D A UD I O A UD I O PRO C E S S I NG D E E P LE A RNI NG E NT E RT A I NM E NT PRO J E C T PYT HO N UNS T RUC T URE D D AT A
Overview
Introduction
“If I were not a physicist, I would probably be a musician. I often think in music. I live my daydreams in music. I see my life in terms of
music.” – Albert Einstein
I might not be a physicist like Mr. Einstein, but I wholeheartedly agree with his thoughts on music! I can’t
remember a single day when I didn’t open up my music player. My travel to and from office is accompanied
by the tune of music and honestly, it helps me focus on my work.
I’ve always dreamed of composing music but didn’t quite get the hang of instruments. That was until I
came across deep learning. Using certain techniques and frameworks, I was able to compose my own
original music score without really knowing any music theory!
This was one of my favorite professional projects. I combined my two passions – music and deep learning
– to create an automatic music generation model. It’s a dream come true!
I am thrilled to share my approach with you, including the entire code to enable you to generate your own
music! We’ll first quickly understand the concept of automatic music generation before diving into the
different approaches we can use to perform this. Finally, we will fire up Python and design our own
automatic music generation model.
Table of Contents
I define music as a collection of tones of different frequencies. So, the Automatic Music Generation is a
process of composing a short piece of music with minimum human intervention.
What could be the simplest form of generating music?
It all started by randomly selecting sounds and combining them to form a piece of music. In 1787, Mozart
proposed a Dice Game for these random sound selections. He composed nearly 272 tones manually! Then,
he selected a tone based on the sum of 2 dice.
Another interesting idea was to make use of musical grammar to generate music.
Musical Grammar comprehends the knowledge necessary to the just arrangement and combination of musical sounds and to the proper
performance of musical compositions
– Foundations of Musical Grammar
In the early 1950s, Iannis Xenakis used the concepts of Statistics and Probability to compose music –
popularly known as Stochastic Music. He defined music as a sequence of elements (or sounds) that
occurs by chance. Hence, he formulated it using stochastic theory. His random selection of elements was
strictly dependent on mathematical concepts.
Recently, Deep Learning architectures have become the state of the art for Automatic Music Generation. In
this article, I will discuss two different approaches for Automatic Music Composition using WaveNet and
LSTM (Long Short Term Memory) architectures.
Note: This article requires a basic understanding of a few deep learning concepts. I recommend going
through the below articles:
Music is essentially composed of Notes and Chords. Let me explain these terms from the perspective of
the piano instrument:
Note: The sound produced by a single key is called a note
Chords: The sound produced by 2 or more keys simultaneously is called a chord. Generally, most
chords contain at least 3 key sounds
Octave: A repeated pattern is called an octave. Each octave contains 7 white and 5 black keys
I will discuss two Deep Learning-based architectures in detail for automatically generating music –
WaveNet and LSTM. But, why only Deep Learning architectures?
Deep Learning is a field of Machine Learning which is inspired by a neural structure. These networks
extract the features automatically from the dataset and are capable of learning any non-linear function.
That’s why Neural Networks are called as Universal Functional Approximators.
Hence, Deep Learning models are the state of the art in various fields like Natural Language Processing
(NLP), Computer Vision, Speech Synthesis and so on. Let’s see how we can build these models for music
composition.
WaveNet is a Deep Learning-based generative model for raw audio developed by Google DeepMind.
The main objective of WaveNet is to generate new samples from the original distribution of the data.
Hence, it is known as a Generative Model.
In a language model, given a sequence of words, the model tries to predict the next word. Similar to a
language model, in WaveNet, given a sequence of samples, it tries to predict the next sample.
Long Short Term Memory Model, popularly known as LSTM, is a variant of Recurrent Neural Networks
(RNNs) that is capable of capturing the long term dependencies in the input sequence. LSTM has a wide
range of applications in Sequence-to-Sequence modeling tasks like Speech Recognition, Text
Summarization, Video Classification, and so on.
Let’s discuss in detail how we can train our model using these two approaches.
This is a Many-to-One problem where the input is a sequence of amplitude values and the output is the subsequent value.
WaveNet takes the chunk of a raw audio wave as an input. Raw audio wave refers to the representation of a
wave in the time series domain.
In the time-series domain, an audio wave is represented in the form of amplitude values which are recorded
at different intervals of time:
Given the sequence of the amplitude values, WaveNet tries to predict the successive amplitude value.
Let’s understand this with the help of an example. Consider an audio wave of 5 seconds with a sampling
rate of 16,000 (that is 16,000 samples per second). Now, we have 80,000 samples recorded at different
intervals for 5 seconds. Let’s break the audio into chunks of equal size, say 1024 (which is a
hyperparameter).
The below diagram illustrates the input and output sequences for the model:
Input and Output of first 3 chunks
We can infer from the above that the output of every chunk depends only on the past information ( i.e.
previous timesteps) but not on the future timesteps. Hence, this task is known as Autoregressive task and
the model is known as an Autoregressive model.
Inference phase
In the inference phase, we will try to generate new samples. Let’s see how to do that:
The building blocks of WaveNet are Causal Dilated 1D Convolution layers. Let us first understand the
importance of the related concepts.
One of the main reasons for using convolution is to extract the features from an input.
For example, in the case of image processing, convolving the image with a filter gives us a feature map.
Convolution is a mathematical operation that combines 2 functions. In the case of image processing,
convolution is a linear combination of certain parts of an image with the kernel.
You can browse through the below article to read more about convolution:
What is 1D Convolution?
The objective of 1D convolution is similar to the Long Short Term Memory model. It is used to solve similar
tasks to those of LSTM. In 1D convolution, a kernel or a filter moves along only one direction:
The output of convolution depends upon the size of the kernel, input shape, type of padding, and stride.
Now, I will walk you through different types of padding for understanding the importance of using Dilated
Causal 1D Convolution layers.
When we set the padding valid, the input and output sequences vary in length. The length of an output is
less than an input:
When we set the padding to same, zeroes are padded on either side of the input sequence to make the
length of input and output equal:
Pros of 1D Convolution:
Cons of 1D Convolution:
When padding is set to the same, output at timestep t is convolved with the previous t-1 and future
timesteps t+1 too. Hence, it violates the Autoregressive principle
When padding is set to valid, input and output sequences vary in length which is required for
computing residual connections (which will be covered later)
Note: The pros and Cons I mentioned here are specific to this problem.
This is defined as convolutions where output at time t is convolved only with elements from time t and earlier in the previous layer.
In simpler terms, normal and causal convolutions differ only in padding. In causal convolution, zeroes are
added to the left of the input sequence to preserve the principle of autoregressive:
Pros of Causal 1D convolution:
Causal convolution does not take into account the future timesteps which is a criterion for building a
Generative model
Causal convolution cannot look back into the past or the timesteps that occurred earlier in the
sequence. Hence, causal convolution has a very low receptive field. The receptive field of a network
refers to the number of inputs influencing an output:
As you can see here, the output is influenced by only 5 inputs. Hence, the Receptive field of the network is
5, which is very low. The receptive field of a network can also be increased by adding kernels of large sizes
but keep in mind that the computational complexity increases.
A Causal 1D convolution layer with the holes or spaces in between the values of a kernel is known as Dilated 1D convolution.
The number of spaces to be added is given by the dilation rate. It defines the reception field of a network.
A kernel of size k and dilation rate d has d-1 holes in between every value in kernel k.
As you can see here, convolving a 3 * 3 kernel over a 7 * 7 input with dilation rate 2 has a reception field of
5 * 5.
As you can see here, the output is influenced by all the inputs. Hence, the receptive field of the network is
16.
A building block contains Residual and Skip connections which are just added to speed up the
convergence of the model:
Another approach for automatic music generation is based on the Long Short Term Memory (LSTM) model.
The preparation of input and output sequences is similar to WaveNet. At each timestep, an amplitude value
is fed into the Long Short Term Memory cell – it then computes the hidden vector and passes it on to the
next timesteps.
The current hidden vector at timestep h t is computed based on the current input a t and previously hidden
vector h t-1 . This is how the sequential information is captured in any Recurrent Neural Network:
Pros of LSTM:
Cons of LSTM:
It consumes a lot of time for training since it processes the inputs sequentially
The wait is over! Let’s develop an end-to-end model for the automatic generation of music. Fire up your
Jupyter notebooks or Colab (or whichever IDE you prefer).
I downloaded and combined multiple classical music files of a digital piano from numerous resources. You
can download the final dataset from here.
Impor t libraries:
Music 21 is a Python library developed by MIT for understanding music data. MIDI is a standard format for
storing music files. MIDI stands for Musical Instrument Digital Interface. MIDI files contain the
instructions rather than the actual audio. Hence, it occupies very little memory. That’s why it is usually
preferred while transferring files.
#library for understanding music from music21 import *
Let’s define a function straight away for reading the MIDI files. It returns the array of notes and chords
present in the musical file.
view raw
music_1.py hosted with ❤ by GitHub
view raw
music_2.py hosted with ❤ by GitHub
Under this section, we will explore the dataset and understand it in detail.
view raw
music_3.py hosted with ❤ by GitHub
Output: 304
As you can see here, no. of unique notes is 304. Now, let us see the distribution of the notes.
1 #importing library
2 from collections import Counter
3
4 #computing frequency of each note
5 freq = dict(Counter(notes_))
6
7 #library for visualiation
8 import matplotlib.pyplot as plt
9
10 #consider only the frequencies
11 no=[count for _,count in freq.items()]
12
13 #set the figure size
14 plt.figure(figsize=(5,5))
15
16 #plot
17 plt.hist(no)
view raw
music_4.py hosted with ❤ by GitHub
Output:
From the above plot, we can infer that most of the notes have a very low frequency. So, let us keep the top
frequent notes and ignore the low-frequency ones. Here, I am defining the threshold as 50. Nevertheless,
the parameter can be changed.
Output: 167
As you can see here, no. of frequently occurring notes is around 170. Now, let us prepare new musical
files which contain only the top frequent notes
1 new_music=[]
2
3 for notes in notes_array:
4 temp=[]
5 for note_ in notes:
6 if note_ in frequent_notes:
7 temp.append(note_)
8 new_music.append(temp)
9
10 new_music = np.array(new_music)
view raw
music_5.py hosted with ❤ by GitHub
Preparing Data:
1 no_of_timesteps = 32
2 x = []
3 y = []
4
5 for note_ in new_music:
6 for i in range(0, len(note_) - no_of_timesteps, 1):
7
8 #preparing input and output sequences
9 input_ = note_[i:i + no_of_timesteps]
10 output = note_[i + no_of_timesteps]
11
12 x.append(input_)
13 y.append(output)
14
15 x=np.array(x)
16 y=np.array(y)
view raw
music_6.py hosted with ❤ by GitHub
enumerate(unique_x))
view raw
music_7.py hosted with ❤ by GitHub
Let us preserve 80% of the data for training and the rest 20% for the evaluation:
train_test_split(x_seq,y_seq,test_size=0.2,random_state=0)
Model Building
I have defined 2 architectures here – WaveNet and LSTM. Please experiment with both the architectures to
understand the importance of WaveNet architecture.
1 def lstm():
2 model = Sequential()
3 model.add(LSTM(128,return_sequences=True))
4 model.add(LSTM(128))
5 model.add(Dense(256))
6 model.add(Activation('relu'))
7 model.add(Dense(n_vocab))
8 model.add(Activation('softmax'))
9 model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')
10 return model
view raw
lstm.py hosted with ❤ by GitHub
I have simplified the architecture of the WaveNet without adding residual and skip connections since the
role of these layers is to improve the faster convergence (and WaveNet takes raw audio wave as input). But
in our case, the input would be a set of nodes and chords since we are generating music:
view raw
10_8.py hosted with ❤ by GitHub
Let’s train the model with a batch size of 128 for 50 epochs:
Its time to compose our own music now. We will follow the steps mentioned under the inference phase for
the predictions.
1 import random
2 ind = np.random.randint(0,len(x_val)-1)
3
4 random_music = x_val[ind]
5
6 predictions=[]
7 for i in range(10):
8
9 random_music = random_music.reshape(1,no_of_timesteps)
10
11 prob = model.predict(random_music)[0]
12 y_pred= np.argmax(prob,axis=0)
13 predictions.append(y_pred)
14
15 random_music = np.insert(random_music[0],len(random_music[0]),y_pred)
16 random_music = random_music[1:]
17
18 print(predictions)
view raw
10_9.py hosted with ❤ by GitHub
The final step is to convert back the predictions into a MIDI file. Let’s define the function to accomplish the
task.
1 def convert_to_midi(prediction_output):
2
3 offset = 0
4 output_notes = []
5
6 # create note and chord objects based on the values generated by the model
7 for pattern in prediction_output:
8
9 # pattern is a chord
10 if ('.' in pattern) or pattern.isdigit():
11 notes_in_chord = pattern.split('.')
12 notes = []
13 for current_note in notes_in_chord:
14
15 cn=int(current_note)
16 new_note = note.Note(cn)
17 new_note.storedInstrument = instrument.Piano()
18 notes.append(new_note)
19
20 new_chord = chord.Chord(notes)
21 new_chord.offset = offset
22 output_notes.append(new_chord)
23
24 # pattern is a note
25 else:
26
27 new_note = note.Note(pattern)
28 new_note.offset = offset
29 new_note.storedInstrument = instrument.Piano()
30 output_notes.append(new_note)
31
32 # increase offset each iteration so that notes do not stack
33 offset += 1
34 midi_stream = stream.Stream(output_notes)
35 midi_stream.write('midi', fp='music.mid')
view raw
10_10.py hosted with ❤ by GitHub
convert_to_midi(predicted_notes)
0:00 / 0:00
0:00 / 0:00
0:00 / 0:00
Awesome, right? But your learning doesn’t stop here. Just remember that we have built a baseline model.
There are plenty of ways to improve the performance of the model even further:
As the size of the training dataset is small, we can fine-tune a pre-trained model to build a robust
system
Collect as much as training data as you can since the deep learning model generalizes well on the
larger datasets
End Notes
Deep Learning has a wide range of applications in our daily life. The key steps in solving any problem are
understanding the problem statement, formulating it and defining the architecture to solve the problem.
I had a lot of fun (and learning) while working on this project. Music is a passion of mine and it was quite
intriguing combining deep learning with that.
I am looking forward to hearing your approach to the problem in the comments section. And if you have
any feedback on this article or any doubts/queries, kindly share them in the comments section below and I
will get back to you.
Aravind Pai
Aravind is a sports fanatic. His passion lies in developing data-driven products for the sports domain.
He strongly believes that analytics in sports can be a game-changer