Lewati ke konten utama

Tutorial Menggubah Musik dengan TensorFlow 2.0

Dalam tutorial ini, Anda akan mempelajari cara melatih model generatif untuk menggubah musik di TensorFlow 2.0.
Diperbarui 29 Sep 2026  · 15 mnt Baca

Jelajahi bersama AI

ChatGPTClaudePerplexity

spanduk pengantar tensorflow di python

Tutorial ini dikembangkan dengan TensorFlow 2.0 di Python, bersama API tingkat tinggi Keras, yang memegang peran lebih besar di TensorFlow 2.0. Bagi Anda yang ingin mempelajari lebih lanjut tentang TensorFlow 2.0, lihat Introduction to TensorFlow in Python di DataCamp. Untuk tinjauan menyeluruh tentang literatur deep learning untuk musik, lihat Briot, Hadjerest, dan Pachet (2019), yang akan kami rujuk sepanjang tutorial ini.

Model Generatif

Model pembelajaran mesin terawasi dapat dibagi menjadi dua kategori: model diskriminatif dan model generatif. Model diskriminatif mengidentifikasi batas keputusan dan menghasilkan klasifikasi yang sesuai. Model generatif menciptakan instance baru dari suatu kelas.

Model diskriminatif untuk musik dapat digunakan untuk mengklasifikasikan lagu ke dalam berbagai genre. Model generatif dapat menggubah lagu dengan genre tertentu. Dalam tutorial ini, kita akan menggunakan model generatif untuk menggubah musik.

Diskriminatif vs. Generatif
Gambar ini menggambarkan model musik diskriminatif dan generatif.

Representasi Musik

Sebelum kita dapat mendefinisikan dan melatih model generatif, kita harus menyusun dataset terlebih dahulu. Untuk musik, data dapat direpresentasikan dalam bentuk kontinyu atau diskret. Bentuk kontinyu yang paling umum adalah sinyal audio, biasanya disimpan sebagai file WAV. Kita tidak akan menggunakan bentuk kontinyu dalam tutorial ini, tetapi Anda dapat membaca lebih lanjut di Lampiran. Bentuk diskret yang umum mencakup file Musical Instrument Digital Interface (MIDI), pianoroll, dan teks. Kita akan berfokus pada file MIDI dan mengekstrak dua jenis objek simbolik: not dan kord.

Not

Not adalah representasi simbolik dari bunyi. Untuk tujuan kita, sebuah not dapat dijelaskan oleh tinggi nada (pitch) dan durasinya. Pitch suatu not berkaitan dengan frekuensi osilasi gelombang bunyinya, yang diukur dalam hertz (Hz). Not dengan pitch lebih tinggi memiliki gelombang bunyi dengan lebih banyak osilasi per detik. Durasi suatu not adalah lamanya periode saat not itu dimainkan.

File MIDI merepresentasikan pitch not dengan bilangan bulat antara 0 dan 127. Not juga dapat direpresentasikan dengan huruf pitch dan nomor oktaf. Dalam oktaf yang sama, pitch diurutkan dari frekuensi terendah ke tertinggi sebagai berikut:

  1. C
  2. C#/D♭
  3. D
  4. E♭/D#
  5. E
  6. F
  7. F#/G♭
  8. G
  9. A♭/G#
  10. A
  11. B♭/A#
  12. B

Oktaf ditandai dengan subskrip, seperti 4 pada A4 atau 7 pada C7. Oktaf yang lebih tinggi berkorelasi dengan frekuensi yang lebih tinggi. Jika kita ambil pitch sembarang, Xi, maka pitch Xi+1, yang tepat satu oktaf lebih tinggi, merepresentasikan gelombang bunyi dengan frekuensi dua kali lipat Xi.

Selain pitch suatu not, kita juga akan memanfaatkan durasinya. Durasi adalah nilai relatif yang dinormalisasi dengan panjang not penuh (whole note). Not terpanjang adalah not "besar", yang delapan kali lebih lama dari not penuh. Not terpendek adalah not dua ratus lima puluh enam, yang panjangnya 1/256 dari not penuh.

Kord

Kord adalah kombinasi dari dua atau lebih not yang dimainkan secara bersamaan pada instrumen yang sama. Jika kita melihat musik monofonik — yaitu musik yang dimainkan pada satu instrumen — kita dapat mengidentifikasi kord dengan mengasumsikan bahwa semua not yang dimainkan pada waktu yang persis sama adalah bagian dari kord yang sama. Asumsi ini tidak valid bila kita memiliki musik polifonik, yang terdiri dari dua atau lebih instrumen yang dimainkan secara bersamaan.

Pada blok kode di bawah, kita memasang music21 lalu mengimpor modul converter, yang akan kita gunakan untuk mengurai file MIDI. Kita akan memuat dan mengurai karya gitar klasik oleh Mauro Giuliani. Kemudian kita terapkan metode .chordify(), yang merekonstruksi urutan kord dalam partitur, dengan asumsi bahwa not yang dimainkan secara bersamaan merupakan bagian dari kord yang sama.

!pip install music21
from music21 import converter

# Define data directory
data_dir = '../audio/'

# Parse MIDI file and convert notes to chords
score = converter.parse(data_dir+'giuliani.mid').chordify()

# Display as sheet music
print(score.show())

Setelah membangun urutan kord dalam file MIDI, kita dapat menampilkannya sebagai partitur menggunakan metode .show(). Ini memerlukan perangkat lunak yang dapat membuat dan menampilkan partitur, seperti MuseScore, yang tersedia gratis.

Partitur yang Dihasilkan dari MIDI
Gambar ini menunjukkan file MIDI yang ditampilkan sebagai partitur.

Atau, kita mungkin ingin menelaah dan memanipulasi elemen dasarnya sebagai teks. Kita dapat melakukannya dengan metode .show('text'). Di awal setiap baris, kita dapat melihat offset dalam tanda kurung, yang memberi tahu kita berapa detik ke dalam partitur sebuah elemen muncul. Perhatikan bahwa hanya satu dari elemen pada detik ke-0 yang merupakan kord. Sisanya adalah metadata, termasuk instrumen yang digunakan, tempo, tangga nada, dan meter. Kita akan memanfaatkan tangga nada nanti, tetapi untuk saat ini Anda dapat mengabaikan elemen-elemen tersebut.

 print(score.show('text'))
{0.0} <music21.instrument.Guitar 'Guitar'>
{0.0} <music21.tempo.MetronomeMark animato Quarter=120.0>
{0.0} <music21.key.Key of C major>
{0.0} <music21.meter.TimeSignature 4/4>
{0.0} <music21.chord.Chord E3 G3>
{1.0} <music21.chord.Chord B2 G3>
{2.0} <music21.chord.Chord D3 G3>
...
{46.0} <music21.chord.Chord A3>
...
{88.0} <music21.chord.Chord G2 E3 G3 C4>
{90.0} <music21.chord.Chord G2 D3 G3 B3>
{92.0} <music21.chord.Chord C3 E3 G3 C4>

Setelah metadata, elemen yang tersisa terdiri dari kord, diurutkan sesuai urutan kemunculannya dalam lagu. Misalnya, <music21.chord.Chord E3 G3> adalah kord yang dihasilkan dengan memainkan not E3 dan G3. Selain itu, <music21.chord.Chord A3> adalah not A3 yang dimainkan sendiri.

Untuk beberapa model, kita akan menggunakan kord beserta durasinya, yang mengukur lamanya kord itu dimainkan. Kita bisa mendapatkan informasi ini dengan menerapkan metode .elements dan mengakses atribut .duration untuk kord sembarang dalam partitur, seperti pada blok kode di bawah.

print(score.elements[10])
<music21.chord.Chord E3 A3>
print(score.elements[10].duration)
<music21.duration.Duration 1.0>

Pengodean Not dan Kord

Sepanjang tutorial ini, kita akan berfokus pada representasi diskret musik yang dapat dijelaskan oleh urutan not, kord, dan durasi terkait. Ingat bahwa kord adalah kombinasi not dan format MIDI memungkinkan penggunaan 128 not individual. Jika kita ingin memperluas himpunan fitur untuk menyertakan semua kord dua not, ukurannya bertambah sebesar 128!/[(128-2)!*2!] atau 8128 elemen. Menambahkan kord tiga not menambah 341.376, dan menambahkan kord empat not menambah lagi 10.668.000.

Contoh yang kita tunjukkan sebelumnya berisi not serta kord dua, tiga, dan empat not. Jika kita menggunakan one-hot encoding dan menyertakan semua fitur, kita harus membuat prediksi atas vektor berdimensi 11.017.632. Konsekuensinya, kita perlu mempertimbangkan dengan cermat pilihan pengodean dalam masalah generasi musik. Kita juga mungkin ingin mempertimbangkan transposisi kunci atau augmentasi data, yang keduanya dibahas di Lampiran.

Terkait pilihan pengodean, Briot, Hadjerest, dan Pachet (2019) membahas empat opsi berbeda yang umum digunakan dalam model generatif musik:

  1. One-Hot Encoding
  2. Many-Hot Encoding
  3. Multi-One-Hot Encoding
  4. Multi-Many-One-Hot Encoding

One-hot encoding merepresentasikan simbol musik, seperti not dan kord, sebagai vektor jarang dengan angka satu pada posisi yang sesuai dengan not atau kord tertentu. Many-hot encoding merepresentasikan kord, yang terdiri dari beberapa not, dengan menempatkan angka satu pada posisi yang sesuai untuk masing-masing not.

Pada diagram di bawah, kita membandingkan many-hot dan one-hot encoding untuk kasus saat kita memiliki not dan kord dua not. Dengan many-hot encoding, not C4 dan D4 direpresentasikan oleh vektor dengan 128 elemen. Kedua vektor bernilai nol di semua posisi selain posisi yang sesuai dengan elemen C4 dan D4, yang bernilai satu. Kord C4+D4, yang merupakan hasil memainkan not C4 dan D4 secara bersamaan, dibentuk dengan menempatkan angka satu pada posisi C4 dan D4.

Pengodean Not dan Kord
Diagram ini mengilustrasikan perbedaan antara one-hot dan many-hot encoding.

Representasi one-hot untuk not tunggal dan kord dua not terdiri dari vektor dengan 8256 elemen. Dalam setiap kasus, vektornya jarang dan memuat 1 pada posisi yang sesuai dengan not atau kord unik. Perhatikan bahwa representasi many-hot memerlukan penggunaan model multi-label, di mana beberapa kelas dapat diprediksi untuk satu keluaran. Namun, ini secara substansial mengurangi ukuran vektor masukan dan keluaran.

Kebanyakan model dalam literatur masih menggunakan one-hot encoding. Namun, Briot, Hadjerest, dan Pachet (2019) mengidentifikasi beberapa model generasi musik yang menggunakan many-hot encoding, termasuk sistem RBMc, RNN-RBM, dan C-RBM, yang semuanya berbasis arsitektur restricted Boltzmann machine (RBM).

Multi-one-hot encoding dan multi-many-hot encoding adalah generalisasi dari one-hot dan many-hot encoding untuk kasus ketika kita memiliki musik polifonik — yaitu, beberapa instrumen dimainkan secara bersamaan. Kita akan membatasi diri pada musik monofonik dan tidak membahas opsi-opsi ini lebih lanjut dalam tutorial.

Pengumpulan dan Persiapan Data

Langkah berikutnya adalah mengumpulkan dataset yang dapat kita gunakan untuk melatih model generatif. Kita akan menggunakan data dari Mutopia Project, yang berisi 2124 karya musik dalam format MIDI yang berada di domain publik. Karena situs web dibagi berdasarkan instrumen, kita dapat menulis skrip Python yang relatif sederhana untuk mengambil semua karya gitar, yang akan kita gunakan untuk melatih model dalam tutorial ini. Untuk ikhtisar cara melakukannya, lihat Lampiran.

Anggaplah kita telah menyimpan semua file .mid dari bagian gitar ke sebuah direktori, save_dir. Langkah berikutnya adalah memuat setiap file .mid ke music21 sebagai objek stream.

import os
from music21 import converter, pitch, interval

# Define save directory
save_dir = '../guitar/'

# Identify list of MIDI files
songList = os.listdir(save_dir)

# Create empty list for scores
originalScores = []

# Load and make list of stream objects
for song in songList:
    score = converter.parse(save_dir+song)
    originalScores.append(score)

Sebagai bagian dari strategi reduksi dimensi, kita akan membatasi diri pada lagu-lagu gubahan Mauro Giuliani. Kita dapat melakukannya dengan memodifikasi blok kode di atas seperti berikut.

# Identify list of MIDI files
songList = os.listdir(save_dir)
songList = [song for song in songList if song.lower().find('giuliani')>-1]

Kita perlu melakukan empat hal tambahan sebelum mulai melatih:

  1. Menghapus musik polifonik dan menerapkan .chordify() pada objek stream yang tersisa.
  2. Ekstrak urutan not, kord, dan durasi.
  3. Menyusun pemetaan yang menerjemahkan not, kord, dan durasi ke bilangan bulat dan sebaliknya.
  4. Membagi stream menjadi urutan berdurasi tetap yang dapat digunakan sebagai masukan ke model.

Mulailah dengan menghapus musik polifonik dari dataset. Kita dapat melakukannya dengan mempartisi setiap objek stream berdasarkan instrumen lalu memeriksa apakah hanya ada satu partisi — yaitu, apakah hanya satu instrumen yang bermain. Kita akan melakukannya pada blok kode di bawah.

from music21 import instrument

# Define function to test whether stream is monotonic
def monophonic(stream):
    try:
        length = len(instrument.partitionByInstrument(stream).parts)
    except:
        length = 0
    return length == 1

# Merge notes into chords
originalScores = [song.chordify() for song in originalScores]

Perhatikan bahwa jika kita menerapkan .chordify() — seperti pada gambar di bawah — tanpa menghapus musik polifonik, kita akan secara tidak sengaja menggabungkan not yang dimainkan oleh instrumen berbeda menjadi sebuah kord.

Mengonversi Not menjadi Kord
Gambar ini menunjukkan not yang diubah menjadi kord.

Sekarang kita perlu mengekstrak not, kord, dan durasi dari setiap stream. Kita akan terlebih dahulu mendefinisikan tiga daftar kosong: originalChords, originalDurations, originalKeys. Kita dapat melakukan enumerasi atas daftar stream. Untuk setiap stream, kita akan mengidentifikasi dan menyimpan semua not, kord, dan durasi, serta tangga nada karya tersebut.

from music21 import note, chord

# Define empty lists of lists
originalChords = [[] for _ in originalScores]
originalDurations = [[] for _ in originalScores]
originalKeys = []

# Extract notes, chords, durations, and keys
for i, song in enumerate(originalScores):
    originalKeys.append(str(song.analyze('key')))
    for element in song:
        if isinstance(element, note.Note):
            originalChords[i].append(element.pitch)
            originalDurations[i].append(element.duration.quarterLength)
        elif isinstance(element, chord.Chord):
            originalChords[i].append('.'.join(str(n) for n in element.pitches))
            originalDurations[i].append(element.duration.quarterLength)
    print(str(i))

Kita akan melakukan reduksi dimensi lebih lanjut pada blok kode di bawah dengan hanya mempertahankan musik dengan tanda kunci C mayor, yang kebetulan paling umum dalam dataset kita. Kita juga akan menggunakan kata "kord" untuk merujuk pada kord dan not di sisa tutorial.

# Create list of chords and durations from songs in C major
cMajorChords = [c for (c, k) in zip(originalChords, originalKeys) if (k == 'C major')]
cMajorDurations = [c for (c, k) in zip(originalDurations, originalKeys) if (k == 'C major')]

Langkah berikutnya adalah mengidentifikasi himpunan unik kord dan durasi. Lalu kita akan menyusun kamus yang memetakan keduanya ke bilangan bulat. Jika kita mencetak jumlah not dan kord unik, kita dapat melihat bahwa langkah-langkah reduksi dimensi yang telah diambil menurunkan jumlahnya menjadi 267 yang masih dapat dikelola.

# Map unique chords to integers
uniqueChords = np.unique([i for s in originalChords for i in s])
chordToInt = dict(zip(uniqueChords, list(range(0, len(uniqueChords)))))

# Map unique durations to integers
uniqueDurations = np.unique([i for s in originalDurations for i in s])
durationToInt = dict(zip(uniqueDurations, list(range(0, len(uniqueDurations)))))

# Print number of unique notes and chords
print(len(uniqueChords))
267

# Print number of unique durations
print(len(uniqueDurations))
9

Setelah kita melatih model dan membuat prediksi, kita ingin memetakan kembali prediksi bilangan bulat kita ke not, kord, dan durasi. Kita akan membalik kamus chordToInt dan durationToInt di bawah ini.

# Invert chord and duration dictionaries
intToChord = {i: c for c, i in chordToInt.items()}
intToDuration = {i: c for c, i in durationToInt.items()}

Terakhir, kita dapat mendefinisikan urutan pelatihan, yang terdiri dari 32 not dan kord berurutan, beserta durasinya. Kita akan terlebih dahulu melakukannya untuk model tipe autoencoder, di mana himpunan fitur dan targetnya sama.

# Define sequence length
sequenceLength = 32

# Define empty arrays for train data
trainChords = []
trainDurations = []

# Construct training sequences for chords and durations
for s in range(len(cMajorChords)):
    chordList = [chordToInt[c] for c in cMajorChords[s]]
    durationList = [durationToInt[d] for d in cMajorDurations[s]]
    for i in range(len(chordList) - sequenceLength):
        trainChords.append(chordList[i:i+sequenceLength])
        trainDurations.append(durationList[i:i+sequenceLength])

Generasi Musik

Pada bagian ini, kita akan menggunakan dataset yang telah kita susun dan siapkan untuk menghasilkan musik. Kita akan mempertimbangkan tiga model generasi musik yang berbeda, dimulai dari yang paling sederhana:

  1. Autoencoder
  2. Variational Autoencoder (VAE)
  3. Model Long Short-Term Memory (LSTM)

Autoencoder

Sebuah autoencoder terdiri dari dua jaringan, yang ditumpuk vertikal dan dihubungkan oleh sebuah vektor laten. Masukan ke autoencoder pertama-tama diteruskan ke model encoder, yang biasanya terdiri dari satu atau lebih lapisan padat (dense). Lapisan terakhir di model encoder disebut vektor laten. Ini adalah bottleneck yang memaksa fitur yang diekstrak di encoder dikompresi menjadi sejumlah kecil fitur laten.

Vektor laten terhubung ke model decoder, yang melakukan upsampling atau dekompresi data. Jumlah node pada setiap lapisan decoder berturut-turut meningkat. Lapisan keluaran akhir memiliki dimensi yang sama dengan lapisan masukan model encoder.

Berbeda dengan model diskriminatif, yang berupaya mengidentifikasi batas keputusan, autoencoder dilatih untuk merekonstruksi data masukan — dalam hal ini, sebuah lagu — seakurat mungkin, dengan tetap tunduk pada batasan ukuran vektor laten. Oleh karena itu, autoencoder menggunakan masukan sebagai target.

Kita akan menggunakan autoencoder untuk membangun model generatif musik pertama kita. Untuk menjaga agar sesederhana mungkin, kita akan mengadopsi arsitektur model MiniBach yang diperkenalkan dalam Briot, Hadjerest, dan Pachet (2019) sebagai penyederhanaan dari model DeepBach. Untuk contoh lain model autoencoder pada generasi musik, lihat sistem DeepHear.

Data masukan, sebagaimana telah kita susun, terdiri dari vektor one-hot yang merepresentasikan not dan kord. Untuk saat ini kita akan mengabaikan durasi. Mengikuti arsitektur MiniBach, kita mulai dengan meratakan vektor masukan.

# Convert to one-hot encoding and swap chord and sequence dimensions
trainChords = tf.keras.utils.to_categorical(trainChords).transpose(0,2,1)

# Convert data to numpy array of type float
trainChords = np.array(trainChords, np.float)

# Flatten sequence of chords into single dimension
trainChordsFlat = trainChords.reshape(nSamples, nChordsSequence)

Selanjutnya kita mendefinisikan jumlah sampel, nSamples, jumlah kord dan not, nChords, dan ukuran dimensi masukan, inputDim. Kita juga menetapkan jumlah fitur laten, latentDim, menjadi 2. Menggunakan jumlah dimensi laten yang rendah akan menurunkan performa, tetapi memungkinkan kita dengan mudah membuat visualisasi ruang laten.

# Define number of samples, chords and notes, and input dimension
nSamples = trainChords.shape[0]
nChords = trainChords.shape[1]
inputDim = nChords * sequenceLength

# Set number of latent features
latentDim = 2

Sekarang kita mendefinisikan arsitektur model, dimulai dengan lapisan masukan untuk jaringan encoder, encoderInput, dan lapisan masukan untuk jaringan decoder, latent. Perhatikan bahwa lapisan latent juga merupakan keluaran dari jaringan encoder. Selanjutnya kita mendefinisikan lapisan padat, encoded, yang menghubungkan masukan ke vektor laten dan lapisan padat lainnya, decoded, yang menghubungkan vektor laten ke vektor keluaran.

Terakhir, kita mendefinisikan model decoder, decoder, dan autoencoder kita, autoencoder. Perhatikan bahwa .Model() memiliki argumen inputs dan outputs. Untuk model decoder, kita meneruskan vektor latent sebagai masukan dan jaringan decoded sebagai output. Untuk autoencoder penuh, kita gunakan encoderInput sebagai masukan dan meneruskan model decoder, yang dievaluasi menggunakan keluaran jaringan encoder, encoded, sebagai masukan.

# Define encoder input shape
encoderInput = tf.keras.layers.Input(shape = (inputDim))

# Define decoder input shape
latent = tf.keras.layers.Input(shape = (latentDim))

# Define dense encoding layer connecting input to latent vector
encoded = tf.keras.layers.Dense(latentDim, activation = 'tanh')(encoderInput)

# Define dense decoding layer connecting latent vector to output
decoded = tf.keras.layers.Dense(inputDim, activation = 'sigmoid')(latent)

# Define the encoder and decoder models
encoder = tf.keras.Model(encoderInput, encoded)
decoder = tf.keras.Model(latent, decoded)

# Define autoencoder model
autoencoder = tf.keras.Model(encoderInput, decoder(encoded))

Arsitektur autoencoder generik ditunjukkan pada gambar di bawah. Bagian atas jaringan adalah encoder, dan bagian bawah adalah decoder. Keduanya dihubungkan oleh vektor laten.

Arsitektur Jaringan Autoencoder
Gambar ini menunjukkan arsitektur autoencoder.

Dengan arsitektur decoder dan autoencoder yang telah didefinisikan, langkah yang tersisa adalah mengompilasi model lalu melatihnya menggunakan metode .fit(). Ada dua detail penting:

  1. Nilai masukan kita akan berupa 0 dan 1, dan nilai terprediksi berupa bilangan riil antara 0 dan 1. Kita dapat menggunakan fungsi rugi binary_crossentropy.
  2. Nilai masukan dan nilai target sama: trainChordsFlat.
# Compile autoencoder model
autoencoder.compile(loss = 'binary_crossentropy', learning_rate = 0.01, optimizer='rmsprop')

# Train autoencoder
autoencoder.fit(trainChordsFlat, trainChordsFlat, epochs = 500)

Langkah terakhir adalah menghasilkan musik. Karena kita menetapkan panjang urutan menjadi 32, autoencoder akan mengambil 32 kord dan not sebagai masukan dan menghasilkan "lagu" berdurasi tetap yang terdiri dari urutan 32 kord dan not. Autoencoder dilatih untuk menghasilkan keluaran yang sangat mirip dengan masukan; namun, tujuan kita adalah menghasilkan musik baru. Untuk melakukannya, kita akan meneruskan vektor laten acak ke model decoder, yang kita definisikan sebagai sub-jaringan dari model autoencoder.

# Generate chords from randomly generated latent vector
generatedChords = decoder(np.random.normal(size=(1,latentDim))).numpy().reshape(nChords, sequenceLength).argmax(0)

Perhatikan bahwa kita membentuk ulang keluaran autoencoder menjadi array dengan dimensi yang sama dengan masukan awal. Lalu kita mengambil argmax() pada dimensi pertama array. Ini mengembalikan nilai bilangan bulat yang berkaitan dengan kord, yang akan kita identifikasi pada blok kode di bawah.

# Identify chord sequence from integer sequence
chordSequence = [intToChord[c] for c in generatedChords]

Terakhir, kita dapat membuat objek stream menggunakan music21, menetapkan instrumen sebagai gitar, menambahkan urutan kord yang dihasilkan model, lalu mengekspor semuanya sebagai file MIDI.

# Set location to save generated music
generated_dir = '../generated/'

# Generate stream with guitar as instrument
generatedStream = stream.Stream()
generatedStream.append(instrument.Guitar())

# Append notes and chords to stream object
for j in range(len(chordSequence)):
    try:
        generatedStream.append(note.Note(chordSequence[j].replace('.', ' ')))
    except:
        generatedStream.append(chord.Chord(chordSequence[j].replace('.', ' ')))

generatedStream.write('midi', fp=generated_dir+'autoencoder.mid')

Gif di bawah memperlihatkan sebagian proses pelatihan autoencoder. Partitur yang dihasilkan pada setiap langkah adalah rekonstruksi yang terkait dengan vektor keadaan laten tertentu. Setiap frame merepresentasikan 5 epoch pelatihan.

Autoencoder: Evolusi Urutan Not dan Kord
Gambar ini menunjukkan pergeseran ruang laten dari waktu ke waktu.

Kita juga dapat memvisualisasikan pergerakan di ruang laten selama pelatihan, seperti pada gif di bawah. Perhatikan bahwa ruang laten sempit dan bergeser sepanjang epoch pelatihan. Ini akan mempersulit pengambilan keadaan laten acak berkualitas tinggi dan akan sangat bermasalah saat kita menggunakan vektor laten berdimensi lebih tinggi. Pada bagian berikut, kita akan menunjukkan cara mengatasinya menggunakan variational autoencoder (VAE).

Autoencoder: Evolusi Ruang Laten
Gambar ini menunjukkan pergeseran ruang laten dari waktu ke waktu.

Terakhir, kita dapat menelaah keluaran audio dari autoencoder kita. Urutan kord dari lagu asli, yang diambil secara acak dari himpunan pelatihan, diberikan terlebih dahulu. Lalu kita menghasilkan urutan kord dari autoencoder menggunakan vektor laten yang terkait dengan urutan kord dari lagu asli, yang menghasilkan lagu di bawahnya.

Contoh: Lagu Asli
Contoh: Lagu yang Dihasilkan

Variational Autoencoder

Walau autoencoder cocok untuk tugas denoising, kompresi, dan dekompresi, mereka tidak berkinerja baik sebagai model generatif. Variational autoencoder (VAE), yang diperkenalkan oleh Kingma dan Welling (2017), dirancang untuk mengatasi kelemahan autoencoder sebagai model generatif dan merupakan salah satu metode yang paling umum digunakan untuk generasi musik. Briot, Hadjerest, dan Pachet (2019) mengidentifikasi empat sistem generasi musik yang dibangun di atas VAE: MusicVAE, VRAE, VRASH, dan GSLR-VAE. Mereka meningkatkan autoencoder sepanjang dua dimensi:

  1. Alih-alih memetakan setiap himpunan masukan ke sebuah titik di ruang laten, VAE memetakan setiap himpunan masukan ke sebuah distribusi normal, yang dicirikan oleh mean dan varians.
  2. Alih-alih membiarkan ruang laten memiliki nilai mean dan varians sembarang, VAE memaksa mean dan logaritma natural varians mendekati 0.

Kita dapat membuat perubahan pertama dengan mengganti vektor keadaan laten dalam autoencoder dengan tiga lapisan:

  1. Lapisan mean
  2. Lapisan (log) varians
  3. Lapisan sampling

Setiap himpunan masukan dipetakan ke satu mean dan logaritma natural varians, yang cukup untuk mencirikan distribusi normal. Model kemudian secara acak menggambar titik dari distribusi di lapisan sampling.

Blok kode di bawah mendefinisikan fungsi untuk membuat lapisan sampling. Fungsi ini menerima mean dan log varians sebagai masukan. Kemudian menggunakan submodul random dari tensorflow untuk menggambar tensor titik, epsilon, dari distribusi normal standar, yang memiliki mean dan log varians 0. Selanjutnya kita ingin mentransformasikan sampel ini menjadi sampel dari distribusi yang dicirikan oleh parameter mean dan logVar. Untuk melakukannya, kita perlu menskalakan epsilon dengan simpangan baku distribusi terkait. Kita dapat menghitung simpangan baku ini dengan membagi logVar dengan 2 lalu mengeksponensialkannya. Kemudian kita tambahkan mean distribusi, mean.

# Define function to generate sampling layer.
def sampling(params):
    mean, logVar = params
    batchSize = tf.shape(mean)[0]
    latentDim = tf.shape(mean)[1]
    epsilon = tf.random.normal(shape=(batchSize, latentDim))
    return mean + tf.exp(logVar / 2.0) * epsilon

Sekarang kita dapat memodifikasi model autoencoder untuk memasukkan lapisan mean, logVar, dan sampling. Perhatikan bahwa lapisan encoded kini menerima dua masukan dan lapisan encoder memiliki tiga keluaran.

# Define the input and latent layer shapes.
encoderInput = tf.keras.layers.Input(shape = (inputDim))
latent = tf.keras.layers.Input(shape = (latentDim))

# Add mean and log variance layers.
mean = tf.keras.layers.Dense(latentDim)(encoderInput)
logVar = tf.keras.layers.Dense(latentDim)(encoderInput)

# Add sampling layer.
encoded = tf.keras.layers.Lambda(sampling, output_shape=(latentDim,))([mean, logVar])

# Define decoder layer.
decoded = tf.keras.layers.Dense(inputDim, activation = 'sigmoid')(latent)

# Define encoder and decoder layers.
encoder = tf.keras.Model(encoderInput, [mean, logVar, encoded])
decoder = tf.keras.Model(latent, decoded)

# Define variational autoencoder model.
vae = tf.keras.Model(encoderInput, decoder(encoded))

Seperti pada model autoencoder, keadaan laten kita adalah keluaran dari lapisan encoded. Namun, ada perbedaan penting: alih-alih memetakan setiap himpunan masukan ke satu keadaan laten, kita menggunakan lapisan sampling untuk memetakannya ke nilai acak yang diambil dari distribusi normal dengan parameter mean dan logVar. Ini berarti masukan yang sama akan terkait dengan distribusi keadaan laten.

Langkah terakhir adalah menyesuaikan fungsi rugi untuk memberi disiplin pada pilihan nilai mean dan logVar model. Secara khusus, kita akan menghitung divergensi Kullback-Leibler (KL) antara setiap distribusi yang didefinisikan oleh lapisan mean dan logVar, dan distribusi normal standar, yang memiliki mean dan log varians 0. Semakin jauh mean dan log varians kita dari 0, semakin tinggi komponen rugi KL divergence.

Pertanyaan alami adalah mengapa kita ingin memaksa setiap distribusi mendekati normal standar. Alasannya adalah kita ingin melakukan sampling keadaan laten dan menggunakannya untuk menghasilkan musik. Jika kita menerapkan penalti KL divergence ke fungsi rugi, ini akan memungkinkan kita menggunakan sampel independen dari distribusi normal standar untuk memperoleh keadaan laten yang mungkin menghasilkan karya musik berkualitas tinggi.

Pada blok kode di bawah, kita menunjukkan cara memodifikasi fungsi rugi untuk memasukkan suku KL divergence. Komponen pertama dari fungsi rugi adalah rugi binary crossentropy, dihitung pada masukan dan keluaran dari model vae. Ini juga yang kita gunakan pada autoencoder. Disebut "rugi rekonstruksi" karena memberi penalti bila gagal merekonstruksi lagu asli. Suku KL adalah rata-rata KL divergence antara distribusi ruang laten dan distribusi normal standar. Kita gabungkan keduanya untuk menghitung rugi total, vaeLoss, menambahkannya ke model, mengompilasi, lalu melatih.

# Define the reconstruction loss.
reconstructionLoss = tf.keras.losses.binary_crossentropy(vae.inputs[0], vae.outputs[0])

# Define the Kullback-Liebler divergence term.
klLoss = -0.5 * tf.reduce_mean(1 + logVar - tf.square(mean) - tf.exp(logVar), axis = -1)

# Combine the reconstruction and KL loss terms.
vaeLoss = reconstructionLoss + klLoss

# Add the loss to the model.
vae.add_loss(vaeLoss)

# Compile the model.
vae.compile(batch_size = batchSize, learning_rate = 0.01, optimizer='rmsprop')

# Train the model.
vae.fit(trainChordsFlat, epochs = 500)

Pada bagian sebelumnya, kita menunjukkan bahwa ruang laten autoencoder tidak berperilaku baik. Salah satu tujuan VAE adalah mengatasi cacat desain ini dalam konteks generatif. Gambar di bawah menunjukkan ruang laten untuk VAE. Kita dapat melihat adanya dispersi signifikan pada kedua fitur. Selain itu, keduanya berpusat di sekitar 0 dan tidak bergeser sepanjang epoch pelatihan. Ini akan memudahkan pengambilan keadaan laten acak berkualitas tinggi.

VAE: Evolusi Ruang Laten
Gambar ini menunjukkan pergeseran ruang laten dari waktu ke waktu.

Terakhir, kita beralih ke generasi musik. Kita menggambar vektor laten acak dari distribusi normal standar, meneruskannya ke decoder, membentuk ulang keluaran, dan mengambil argmax dari setiap kolom. Ini akan mengembalikan urutan 32 bilangan bulat, yang berkaitan dengan not dan kord, yang selanjutnya kita identifikasi menggunakan kamus intToChord. Lalu kita mendefinisikan objek stream, menambahkan gitar sebagai instrumen, menambahkan urutan not dan kord ke stream, lalu mengekspornya sebagai file MIDI.

# Generate integers from randomly drawn latent state.
generatedChords = decoder(np.random.normal(size=(1,latentDim))).numpy().reshape(nChords, sequenceLength).argmax(0)

# Identify chords associated with integers.
chordSequence = [intToChord[c] for c in generatedChords]

# Initialize stream with guitar as instrument.
generatedStream = stream.Stream()
generatedStream.append(instrument.Guitar())

# Append notes and chords and export to MIDI file
for j in range(len(chordSequence)):
    try:
        generatedStream.append(note.Note(chordSequence[j].replace('.', ' ')))
    except:
        generatedStream.append(chord.Chord(chordSequence[j].replace('.', ' ')))

generatedStream.write('midi', fp=generated_dir+'vae.mid')

Tiga lagu di bawah dihasilkan oleh model. Dua lagu pertama didasarkan pada keadaan laten yang terkait dengan urutan kord di himpunan pelatihan. Lagu terakhir adalah kombinasi linear dari keadaan laten lagu #1 dan lagu #2. Karena VAE memaksa ruang laten berperilaku baik, kita dapat cukup yakin bahwa kombinasi linear keadaan laten akan menghasilkan keadaan laten berkualitas tinggi.

Contoh: Lagu yang Dihasilkan #1
Contoh: Lagu yang Dihasilkan #2
Contoh: Kombinasi Linear Lagu #1 dan #2

Satu hal yang dapat kita amati dari musik berbasis autoencoder dan VAE adalah kurangnya melodi yang kuat. Kita dapat meningkatkannya dengan menambah lapisan pada model dan memperbesar ukuran himpunan pelatihan. Atau, kita dapat menggunakan model yang dirancang untuk memproses data berurutan, seperti yang akan kita lakukan pada bagian berikut.

Long Short-Term Memory (LSTM)

Model terakhir yang akan kita bahas adalah model long short-term memory (LSTM). Ini adalah jenis recurrent neural network (RNN) yang dimodifikasi untuk mencegah masalah gradien lenyap (vanishing gradient). Briot, Hadjerest, dan Pachet (2019) menemukan bahwa model rekuren adalah yang paling umum digunakan untuk tujuan generasi musik. Mereka mengidentifikasi lebih dari 20 sistem generasi musik yang bergantung pada model RNN atau LSTM, termasuk BachBot, DeepBach, Performance-RNN, dan Hexahedria.

Menggunakan model LSTM menawarkan dua manfaat dibanding model berbasis autoencoder:

  1. Mereka dirancang untuk memproses data berurutan.
  2. Mereka dapat menghasilkan urutan musik dengan panjang berapa pun.

Tidak seperti autoencoder dan VAE, yang menggunakan masukan sebagai target, model generatif musik berbasis LSTM dilatih untuk membuat prediksi tentang not atau kord berikutnya dalam urutan. Karena itu, kita perlu menyusun ulang himpunan latih dan target, yang akan kita lakukan pada blok kode di bawah. Karena penggunaan model LSTM juga akan mengurangi jumlah parameter yang perlu kita latih, kita juga akan menambahkan durasi ke model.

# Set sequence length
sequenceLength = 32

# Define empty array for train data
trainChords = []
trainDurations = []
targetChords = []
targetDurations = []

# Construct train and target sequences for chords and durations
for s in range(len(cMajorChords)):
    chordList = [chordToInt[c] for c in cMajorChords[s]]
    durationList = [durationToInt[d] for d in cMajorDurations[s]]
    for i in range(len(chordList) - sequenceLength):
        trainChords.append(chordList[i:i+sequenceLength])
        trainDurations.append(durationList[i:i+sequenceLength])
        targetChords.append(chordList[i+1])
        targetDurations.append(durationList[i+1])

Perhatikan bahwa trainChords dan trainDurations didefinisikan dengan cara yang sama seperti sebelumnya. Kita hanya menambahkan targetChords dan targetDurations, yang terdiri dari kord dan durasi yang mengikuti kord dan durasi dalam urutan latih. Selanjutnya kita menetapkan jumlah sampel, kord, dan durasi, serta dimensi masukan. Kita juga akan menggunakan lapisan embedding terpisah untuk kord dan durasi.

# Define number of samples, notes and chords, and durations
nSamples = trainChords.shape[0]
nChords = trainChords.shape[1]
nDurations = trainDurations.shape[1]

# Set the input dimension
inputDim = nChords * sequenceLength

# Set the embedding layer dimension
embedDim = 64

Langkah berikutnya adalah mendefinisikan arsitektur model. Kita akan menggunakan model dengan dua masukan dan dua keluaran, sehingga kita dapat memprediksi kord dan durasi. Kita terlebih dahulu mendefinisikan lapisan masukan, chordInput dan durationInput, lalu meneruskan lapisan-lapisan ini ke lapisan embedding, chordEmbedding dan durationEmbedding. Ini adalah tabel pencarian yang mengasosiasikan vektor 64 elemen dengan setiap kord atau durasi. Embedding memungkinkan kita mengganti representasi vektor jarang, di mana masukan di-one-hot, dengan representasi padat, di mana not dan durasi yang terkait memiliki nilai yang lebih berdekatan satu sama lain.

Keluaran dari dua lapisan embedding digabungkan pada mergeLayer. Lalu kita meneruskan keluaran lapisan ini ke dua lapisan LSTM. Lapisan pertama menetapkan parameter return_sequences menjadi True. Jika kita biarkan parameter ini sebagai False, seperti bawaan, sel LSTM hanya akan mengembalikan keadaan akhir dan bukan keadaan sekuensial antara. Pekerjaan terbaru menunjukkan bahwa memanfaatkan keadaan antara dapat meningkatkan performa. Ini sering dilakukan dalam konteks lapisan atensi. Akhirnya, kita meneruskan keluaran lapisan LSTM ke lapisan padat, yang kemudian diteruskan ke dua lapisan keluaran: chordOutput dan durationOutput. Lalu kita mendefinisikan model API fungsional keras dengan menentukan dua lapisan masukan dan dua lapisan keluaran.

# Define input layers
chordInput = tf.keras.layers.Input(shape = (None,))
durationInput = tf.keras.layers.Input(shape = (None,))

# Define embedding layers
chordEmbedding = tf.keras.layers.Embedding(nChords, embedDim, input_length = sequenceLength)(chordInput)
durationEmbedding = tf.keras.layers.Embedding(nDurations, embedDim, input_length = sequenceLength)(durationInput)

# Merge embedding layers using a concatenation layer
mergeLayer = tf.keras.layers.Concatenate(axis=1)([chordEmbedding, durationEmbedding])

# Define LSTM layer
lstmLayer = tf.keras.layers.LSTM(512, return_sequences=True)(mergeLayer)

# Define dense layer
denseLayer = tf.keras.layers.Dense(256)(lstmLayer)

# Define output layers
chordOutput = tf.keras.layers.Dense(nChords, activation = 'softmax')(denseLayer)
durationOutput = tf.keras.layers.Dense(nDurations, activation = 'softmax')(denseLayer)

# Define model
lstm = tf.keras.Model(inputs = [chordInput, durationInput], outputs = [chordOutput, durationOutput])

Dengan model yang telah didefinisikan, kini kita dapat mengompilasi dan mulai melatih. Kami berupaya menjaga sesederhana mungkin di sini, tetapi untuk banyak aplikasi, akan masuk akal untuk menyertakan lapisan regularisasi dan lapisan dense serta LSTM tambahan. Perhatikan bahwa model memiliki dua lapisan masukan dan dua lapisan keluaran, seperti yang ditunjukkan pada diagram di bawah. Anda dapat mempelajari lebih lanjut tentang model dengan beberapa masukan dan keluaran di Introduction to TensorFlow in Python.

Arsitektur Model LSTM
Gambar ini menunjukkan arsitektur model LSTM.
# Compile the model
lstm.compile(loss='categorical_crossentropy', optimizer='rmsprop')

# Train the model
lstm.fit([trainChords, trainDurations], [targetChords, targetDurations],
            epochs=500, batch_size=64)

Setelah proses pelatihan selesai, kita dapat menghasilkan lagu baru menggunakan model LSTM. Kita akan melakukannya dengan memberi masukan urutan awal 32 kord dan durasi, yang memungkinkan kita membuat prediksi pertama. Kemudian kita menambahkan prediksi tersebut ke deret kord dan durasi, sehingga kita dapat membuat prediksi lain, berdasarkan 32 kord dan durasi terakhir dari deret yang diperbarui. Salah satu manfaat menggunakan model LSTM adalah kita dapat beriterasi tanpa batas, sehingga dapat menghasilkan lagu dengan panjang sekehendak.

# Define initial chord and duration sequences
initialChords = np.expand_dims(trainChords[0,:].copy(), 0)
initialDurations = np.expand_dims(trainDurations[0,:].copy(), 0)

# Define function to predict chords and durations
def predictChords(chordSequence, durationSequence):
    predictedChords, predictedDurations = model.predict(model.predict([chordSequence, durationSequence]))
    return np.argmax(predictedChords), np.argmax(predictedDurations)

# Define empty lists for generated chords and durations
newChords, newDurations = [], []

# Generate chords and durations using 500 rounds of prediction
for j in range(500):
    newChord, newDuration = predictChords(initialChords, initialDurations)
    newChords.append(newChord)
    newDurations.append(newDuration)
    initialChords[0][:-1] = initialChords[0][1:]
    initialChords[0][-1] = newChord
    initialDurations[0][:-1] = initialDurations[0][1:]
    initialDurations[0][-1] = newDuration

Langkah terakhir adalah mengekspor musik yang dihasilkan ke file MIDI menggunakan music21. Perhatikan bahwa kodenya hampir identik dengan yang kita gunakan untuk model berbasis autoencoder, tetapi dengan satu perbedaan penting: sekarang kita menambahkan kord dan durasi ke objek stream.

# Create stream object and add guitar as instrument
generatedStream = stream.Stream()
generatedStream.append(instrument.Guitar())

# Add notes and durations to stream
for j in range(len(chordSequence)):
    try:
        generatedStream.append(note.Note(chordSequence[j].replace('.', ' '), quarterType = durationSequence[j]))
    except:
        generatedStream.append(chord.Chord(chordSequence[j].replace('.', ' '), quarterType = durationSequence[j]))

# Export as MIDI file
generatedStream.write('midi', fp=generated_dir+'lstm.mid')

Dengan model autoencoder dan VAE, kita kesulitan menghasilkan melodi dan hanya dapat mengeluarkan lagu berdurasi tetap. Menggunakan model LSTM, kita dapat menghasilkan urutan keluaran dengan panjang arbitrer dengan beriterasi atas prediksi, seperti yang telah kita lakukan di blok kode di atas. Akan relatif lebih mudah pula menghasilkan melodi yang lebih baik. Kita akan melihat kedua fitur ini pada contoh di bawah, masing-masing dipicu dengan urutan awal kord dan durasi dari sampel di himpunan pelatihan kita.

Contoh: Lagu yang Dihasilkan #1
Contoh: Lagu yang Dihasilkan #2
Contoh: Lagu yang Dihasilkan #3

   spanduk kursus pengantar tensorflow di python

Isu Tambahan

Data

Salah satu tantangan utama dalam pengembangan model pembelajaran mesin untuk musik adalah ketersediaan dataset, karena musik umumnya tidak tersedia bebas dan legal untuk diunduh. Faktanya, hal ini telah mencegah literatur untuk berkoordinasi pada dataset praktik standar, seperti dataset MNIST untuk klasifikasi gambar. Namun, ada beberapa proyek yang menyediakan audio mentah, notasi simbolik, atau fitur musik turunan untuk banyak lagu. Kami menyediakan daftar sebagian proyek-proyek tersebut di bawah.

  1. Mutopia Project. Arsip musik domain publik dan creative commons dalam format MIDI, PDF, dan LilyPond. Saat ini berisi lebih dari 2000 lagu dan diorganisasi berdasarkan instrumen, komponis, gaya, dan koleksi.
  2. The Million Song Dataset. Berisi fitur audio dan metadata untuk lebih dari 300gb trek musik populer, tetapi tanpa trek audio.
  3. Free Music Archive. Berisi audio, label genre, dan fitur untuk lebih dari 100.000 trek yang tersedia bebas dan legal untuk diunduh.

Sampling vs Determinisme

Saat kita menghasilkan musik menggunakan model LSTM, kita menggunakan pendekatan deterministik untuk pemilihan kord dan durasi. Model memprediksi probabilitas untuk masing-masing 267 kord dan 9 durasi, dan kita memilih kord dan durasi dengan probabilitas tertinggi. Demikian pula, untuk model autoencoder dan variational autoencoder, kita memilih kord dengan nilai keluaran tertinggi.

Salah satu alternatif pendekatan ini adalah melakukan sampling dari distribusi keluaran, alih-alih memilih kord dan durasi dengan probabilitas tertinggi. Ini akan membuat proses generasi musik menjadi stokastik, bukan deterministik. Artinya, model autoencoder atau VAE dengan vektor laten yang sama akan menghasilkan lagu berbeda setiap kali dijalankan. Demikian pula, urutan masukan yang sama ke model LSTM akan dapat menghasilkan lagu berbeda.

Blok kode di bawah menunjukkan bagaimana kita dapat beralih ke generasi kord dan durasi stokastik dengan memodifikasi fungsi predictChords(). Perhatikan bahwa kita mengganti operasi argmax() dengan operasi sampling yang menggunakan probabilitas untuk melakukan pengambilan acak dari vektor kord dan durasi.

# Define function to generate chords and durations stochastically
def predictChords(chordSequence, durationSequence):
    predictedChords, predictedDurations = model.predict([chordSequence, durationSequence])
    return np.random.choice(range(nChords), p = predictedChords), np.random.choice(range(nDurations), p = predictedDurations)

Untuk pembahasan panjang tentang manfaat pendekatan stokastik dan deterministik untuk generasi musik, lihat bagian 6.6 dari Briot, Hadjerest, dan Pachet (2019).

Embedding

Saat melatih model LSTM, kita menggunakan lapisan embedding, yang memetakan setiap kord dan durasi masukan ke vektor padat berisi 64 elemen. Menggunakan lapisan embedding memungkinkan kita menghindari penggunaan vektor jarang berdimensi tinggi sebagai masukan. Sebagai gantinya, kita menggunakan representasi berdimensi rendah yang memungkinkan kita mengidentifikasi hubungan antara berbagai kord.

Dalam banyak kasus, tidak masuk akal untuk melatih lapisan embedding. Ini karena kita sering kali tidak memiliki himpunan pelatihan yang cukup besar saat mengerjakan masalah generasi musik. Menggunakan embedding pralatih atau embedding yang dihasilkan dengan pembelajaran tanpa pengawasan — seperti yang sering kita lakukan untuk masalah klasifikasi dan generasi teks — akan memungkinkan kita melatih model ketika sebaliknya tidak mungkin dilakukan.

Lihat Chuan, Agres, dan Herremans (2018) untuk ikhtisar cara membangun embedding menggunakan pendekatan pembelajaran tanpa pengawasan.

Generative Adversarial Networks

Kita telah membahas autoencoder, VAE, dan model LSTM sebagai opsi untuk generasi musik. Opsi lain yang telah terbukti sukses untuk pemodelan generatif di domain lain adalah generative adversarial network (GAN). GAN bekerja dengan menggabungkan jaringan diskriminatif dengan jaringan generatif. Jaringan generatif menciptakan sampel, seperti urutan kord yang dihasilkan model LSTM dan autoencoder kita. Sampel ini kemudian digabung dalam himpunan pelatihan bersama sampel asli — yaitu urutan kord dari lagu sebenarnya. Model diskriminatif mencoba mengklasifikasikan sampel apakah asli atau dibuat oleh generator. Generator berusaha mengecoh model diskriminatif agar salah mengklasifikasikan sampel yang dibuatnya sebagai lagu asli. Model dilatih hingga kita mencapai keseimbangan evolusioner yang stabil, di mana baik diskriminator maupun generator tidak dapat memperoleh keuntungan lebih lanjut.

Bagi yang tertarik menerapkan GAN untuk tugas generasi musik, Anda mungkin ingin melihat sumber daya berikut:

  1. MuseGAN adalah proyek yang ditujukan untuk generasi musik polifonik menggunakan GAN. Paketnya mencakup model pralatih dan 174.154 lagu yang telah dibersihkan dan disiapkan dalam format pianoroll.

  2. Magenta adalah proyek open source yang ditujukan pada penerapan pemodelan generatif untuk tugas kreatif, termasuk pembuatan musik. Anda dapat menjelajahi rangkaian alat yang ditawarkan Magenta melalui notebook Colab.

Sumber Daya

Bagi yang ingin mempelajari lebih lanjut tentang TensorFlow 2.0, lihat Introduction to TensorFlow in Python di DataCamp. Untuk tinjauan terbaru dan komprehensif tentang metode generasi musik dengan deep learning, lihat Briot, Hadjerest, dan Pachet (2019). Untuk tutorial tambahan tentang menghasilkan musik dengan model deep learning, lihat 1, 2, 3, 4, dan 5, yang menyediakan pembahasan buku teks lengkap tentang model generatif.

Lampiran

Representasi Kontinyu

File WAV adalah bentuk representasi kontinyu yang paling umum karena mengandung sinyal audio mentah yang tidak terkompresi. Ini berbeda dengan misalnya file mp3, yang mengompresi ukuran file dengan membuang sebagian informasi sinyal audio.

Blok kode di bawah menunjukkan cara mengekstrak laju sampel, rate, dan sinyal audio mentah, signal, dari file WAV yang berisi karya musik gubahan Mozart. Laju sampel adalah jumlah kali sinyal audio diambil sampelnya setiap detik. Dalam kasus kita, nilainya 44100.

from scipy.io import wavfile

# Define data directory
data_dir = '../audio/'

# Extract sample rate and signal
rate, signal = wavfile.read(data_dir+'mozart.wav')

# Bound signal between -1 and 1
normalized_signal = signal / abs(max(signal))

Sinyal audio mentah, signal, adalah tensor S x C, di mana S adalah jumlah sampel dan C adalah jumlah kanal. Untuk kesederhanaan, kita akan mempertimbangkan kasus di mana C = 1. Plot di bawah menunjukkan interval tiga detik dari signal.

Gambar ini menunjukkan plot sinyal audio ternormalisasi yang diekstrak dari file WAV.

Secara umum, menggunakan sinyal audio mentah untuk melatih model jauh lebih menantang dibanding menggunakan representasi diskret. Sebuah survei terbaru tentang metode deep learning untuk generasi musik menelaah 32 metode generasi musik yang berbeda dalam literatur. Di antaranya, hanya tiga yang menggunakan representasi kontinyu:

  1. Audio Style Transfer
  2. DeepAuto-Controller
  3. WaveNet

Transposisi Kunci

Salah satu metode yang dapat digunakan untuk reduksi dimensi atau augmentasi data adalah transposisi kunci. Ini melibatkan penggeseran pitch setiap not dengan interval tetap. Jika, misalnya, sebuah lagu berisi urutan di mana pitch C3 diikuti oleh pitch D3 dan kita menggunakan transposisi yang menggeser semua elemen naik dua pitch, maka C3 menjadi D3 dan D3 menjadi E3. Untuk model yang menggunakan transposisi kunci, lihat Lim, Rhyu, dan Lee (2018) dan Sturm dkk. (2016).

Jika kita menggunakan transposisi kunci untuk tujuan reduksi dimensi, tujuan kita adalah menggeser not dan kord sehingga meminimalkan jumlah total not dan kord unik dalam dataset. Karena musik cenderung ditulis di sekitar kelompok pitch tertentu yang disebut kunci yang terdengar bagus saat dimainkan bersama, kita dapat dengan mudah menggeser setiap lagu dalam dataset ke kunci yang sama.

Kali ini, kita akan mengimpor tiga submodul dari music21:

  1. converter, yang akan kita gunakan untuk memuat file MIDI dan mengonversinya menjadi objek stream.
  2. pitch, yang akan kita gunakan untuk membuat pitch target.
  3. interval, yang akan kita gunakan untuk menghitung jarak antara kunci asli dan kunci target.
from music21 import converter, pitch, interval

# Define data directory
data_dir = '../audio/'

# Parse MIDI file
score = converter.parse(data_dir+'giuliani.mid')

# Identify and print original key
key = score.analyze('key')
print(key)
C major

# Compute interval between original key and target key
keyInterval = interval.Interval(key.tonic, pitch.Pitch('F'))

# Transpose song into F major
newScore = score.transpose(keyInterval)

# Print new key
print(newScore.analyze('key'))
F major

Kita pertama-tama menerapkan metode .analyze('key') pada score, yang mengidentifikasi kunci objek stream, yaitu C mayor. Lalu kita menghitung interval antara C mayor dan kunci target, F mayor. Perhatikan bahwa kita mengakses pitch dari objek key menggunakan .tonic. Selain itu, kita membuat pitch target menggunakan pitch.Pitch('F'). Kemudian kita menetapkan keluaran ke keyInterval, yang memberi tahu kita jarak antara kunci asli dan target.

Terakhir, kita melakukan transposisi kunci menggunakan metode .transpose() dan meneruskan interval, keyInterval. Mencetak kunci baru mengonfirmasi bahwa kuncinya adalah F mayor.

Transposisi Oktaf

Jika kita ingin melangkah lebih jauh dari transposisi kunci, kita juga dapat mentransposisi oktaf menjadi lebih tinggi atau lebih rendah. Alih-alih mentransposisi C mayor ke F mayor, misalnya, kita dapat mentransposisi C mayor ke C mayor pada oktaf yang lebih rendah atau lebih tinggi. Jika kita menargetkan himpunan oktaf yang sama untuk setiap lagu, kita akan memaksa semua not dan kord untuk berkumpul, mengurangi dimensi masalah dan menyederhanakan proses pelatihan model. Pada blok kode di atas, kita dapat mencapainya dengan menentukan oktaf pada pitch. Misalnya, alih-alih menentukan pitch.Pitch('F') untuk menghitung interval, kita dapat menggunakan pitch.Pitch('F3').

Invariansi Kunci dan Oktaf

Alternatif alami untuk reduksi dimensi adalah augmentasi data. Artinya, alih-alih mengurangi himpunan masukan dan keluaran yang mungkin, kita memperluas dataset dengan membuat perubahan pada data yang ada. Kita juga dapat melakukannya menggunakan transposisi kunci dan oktaf. Namun, alih-alih mentransposisi semua lagu ke kunci umum dan mengelompokkan notnya dalam oktaf yang berdekatan, kita justru mentransposisi semua lagu ke semua kunci dan semua oktaf. Ini memungkinkan kita mencapai invariansi kunci dan oktaf pada model yang kita kembangkan. Lihat Briot, Hadjerest, dan Pachet (2019) untuk pembahasan penggunaan transposisi untuk dua tujuan berbeda ini.

Pengumpulan Data

Kita akan mengumpulkan file MIDI untuk melatih model dari Project Mutopia. Sebelum melakukan scraping, kita terlebih dahulu memeriksa file robots, yang terletak di https://www.mutopiaproject.org/robots.txt, untuk memastikan kita menghormati semua permintaan tentang cara penanganan interaksi otomatis dengan situs web. Pada saat tutorial ini ditulis, file robots memuat teks pada blok kode di bawah. User-agent: * menunjukkan bahwa aturan berikutnya berlaku untuk semua pengguna yang berinteraksi dengan situs. Disallow:, diikuti tanpa apa pun, berarti tidak ada pembatasan pada scraping.

# Allow crawling of all content
User-agent: *
Disallow:

Kita akan terlebih dahulu mengimpor urlopen dan urlretrieve dari urllib.request, yang akan kita gunakan untuk mengirim permintaan GET untuk setiap halaman di bagian gitar dan kemudian mengunduh setiap file MIDI yang ditautkan pada halaman. Kita mengimpor BeautifulSoup untuk mengurai HTML yang dikembalikan oleh urlopen, sehingga memungkinkan kita mengidentifikasi tautan file MIDI. Terakhir, kita akan menggunakan modul time untuk jeda selama 10 detik di antara unduhan agar tidak membebani sumber daya situs web.

Gambar ini menunjukkan halaman di bagian gitar situs Mutopia dengan File MIDI yang diidentifikasi.

Setiap langkah loop utama akan memeriksa apakah jumlah tautan pada halaman linkCount melebihi 0. Jika ya, loop akan membangun URL untuk halaman berikutnya yang akan dituju, url, menggunakan string url0, url1, dan nomor lagu, songNumber. Kemudian akan membuka url tersebut, mengurai HTML, menemukan semua tautan pada halaman, mengatur ulang linkCount ke 0, lalu menelusuri setiap tautan dalam daftar. Jika sebuah tautan memuat substring .mid, maka tautan tersebut mengarah ke file MIDI dan diunduh menggunakan urlretrieve() ke direktori yang ditentukan oleh save_dir. Terakhir, kita menambah songNumber sebesar 10, karena setiap halaman memuat 10 lagu. Proses ini diulang hingga kita menjumpai halaman yang memuat kurang dari 10 tautan.

!pip install bs4
from urllib.request import urlopen, urlretrieve
from bs4 import BeautifulSoup
import time

# Define save directory.
save_dir = '../guitar/'

# Define URL components
url0 = 'https://www.mutopiaproject.org/cgibin/make-table.cgi?startat='
url1 = '&searchingfor=&Composer=&Instrument=Guitar&Style=&collection=&id=&solo=&recent=&timelength=&timeunit=&lilyversion=&preview='

# Set initial values
songNumber = 0
linkCount = 10

# Locate and download each MIDI file
while linkCount > 0:
    url = url0 + str(songNumber) + url1
    html = urlopen(url)
    soup = BeautifulSoup(html.read())
    links = soup.find_all('a')
    linkCount = 0
    for link in links:
        href = link['href']
        if href.find('.mid') >= 0:
            linkCount = linkCount + 1
            urlretrieve(href, save_dir+href)
    songNumber += 10
    time.sleep(10.0)

Bagi yang ingin mempelajari lebih lanjut tentang scraping, lihat kursus DataCamp Web Scraping with Python dan tutorial Web Scraping using Python.

Topik
Python
Machine Learning

Pelajari lebih lanjut tentang Python dan Machine Learning

{count, plural, one {Kursus} other {Kursus}}

Pendahuluan TensorFlow di Python

{hours} jam
56.5K
Pelajari dasar-dasar jaringan saraf tiruan dan cara membangun model pembelajaran mendalam menggunakan TensorFlow.
Lihat DetailRight Arrow
Mulai Kursus
Lihat SelengkapnyaRight Arrow
Terkait

{count, plural, one {blog} other {blogs}}

Tutorial Korelasi di R

Dapatkan pengenalan dasar-dasar korelasi di R: pelajari lebih lanjut tentang koefisien korelasi, matriks korelasi, plotting korelasi, dan sebagainya.
David Woods's photo

David Woods

13 mnt

{count, plural, one {blog} other {blogs}}

12 Alternatif ChatGPT Terbaik yang Bisa Anda Coba pada 2026

Artikel ini menyajikan daftar alternatif ChatGPT yang akan meningkatkan produktivitas Anda.
Javier Canales Luna's photo

Javier Canales Luna

14 mnt

{count, plural, one {blog} other {blogs}}

Spaghetti Plot dan Jalur Badai

Temukan alasan mengapa Anda sebaiknya (tidak) menggunakan spaghetti plot untuk menyampaikan ketidakpastian jalur prediksi badai serta dampaknya terhadap interpretasi.
Hugo Bowne-Anderson's photo

Hugo Bowne-Anderson

13 mnt

{count, plural, one {blog} other {blogs}}

40 Pertanyaan Wawancara DBMS Teratas di 2026

Kuasai pertanyaan wawancara basis data, dari konsep SQL dasar hingga skenario desain sistem tingkat lanjut. Panduan mendalam ini mencakup semua yang Anda perlukan untuk sukses di wawancara DBMS dan meraih peran berikutnya.
Dario Radečić's photo

Dario Radečić

15 mnt

Lihat SelengkapnyaLihat Selengkapnya