Neulich habe ich den Code eines einfachen wiederkehrenden neuronalen Netzwerks untersucht, und aus der Idee heraus, etwas damit anfangen zu wollen, kam mir die Idee, Musik in Dur / Moll zu klassifizieren. .. Natürlich kenne ich mich mit Musik nicht aus, also habe ich zunächst bei Wikipedia nach Dur und Moll gesucht.
Ton (cho, key) ist einer der musikalischen Begriffe. Wenn eine Melodie oder ein Akkord in Verbindung mit einer zentralen Note komponiert wird, wird die Musik als tonal bezeichnet. Im engeren Sinne gibt es in der traditionellen westlichen Musik zwei bekannte Tonarten, Dur-Tonart und Moll-Tonart, die sich aus Klängen der gesamten Tonleiter (diatonische Tonleiter) zusammensetzen. Der Klang von la ist der zentrale Klang.
Die grundlegende Definition lautet wie folgt, aber wie ich in der Grund- und Mittelschule gelernt habe, ist das Dur ein Lied, das "hell und energisch" klingt, während das Moll ein Lied ist, das "dunkel und schwer" klingt. Ich habe untersucht, ob dies programmatisch klassifiziert werden kann.
Die zentrale Note wird auch als Grundton bezeichnet (im Folgenden als Basisschlüssel bezeichnet), am bekanntesten ist jedoch das "C-Dur" mit "C" als Basisschlüssel, das sogenannte "Doremi-Fasolasid". (In Japan wird es auch "C-Dur" genannt.) Das Moll, das mit dem Klang von La beginnt, ist "a-Moll" ("a-Moll"). Ich werde diese beiden Skalen aus Wikipedia zitieren.
Fig. C major scale
Fig. A minor scale
Verstehst du? In diesen beiden Skalen ist die Skala auf einer einfachen Partitur geschrieben, ohne ein scharfes Symbol oder ein flaches Symbol auf der rechten Seite des Höhen-Symbols (Otamajakushi). Mit anderen Worten, C-Dur und A-Moll bestehen aus "gleichen Klangbestandteilen". (Die Elemente sind gleich.) Der Unterschied zwischen den beiden besteht darin, ob der Basisschlüssel "C" oder "A" ist. (A-Moll wird nach unten verschoben.) Übrigens scheint die Beziehung zwischen zwei Skalen mit denselben Klangbestandteilen als "paralleler Ton" bezeichnet zu werden.
Wenn wir uns nun mit der Skala der Musik befassen, weisen wir jedem Klang Zahlen zu.
Fig. Key mapping
Die obige Abbildung zeigt die Tastatur für eine Oktave, aber die Ganzzahlen wie 3, 4, 5 ... werden in der Reihenfolge vom linken "C" (do) zugewiesen. Zahlen werden auch dort zugewiesen, wo schwarze Tasten vorhanden sind. Wenn Sie also nur die weißen Tasten betrachten, sehen Sie eine leicht unregelmäßige Folge von Zahlen, aber wir werden mit der Programmierung auf diese Weise fortfahren.
Ich erwähnte, dass es "C-Dur" und "A-Moll" als typische Dur- und Moll-Tonarten gibt, aber da dies allein als Klassifizierungsproblem langweilig ist, behandeln wir 5 Hauptfächer, 5 Nebenfächer und insgesamt 10 Arten von Musikskalen. , Das Problem, grob in Dur (Dur) und Moll (Moll) unterteilt zu werden, wurde festgelegt.
Wir haben die folgenden 10 Arten von Waagen vorbereitet. Es gibt 5 Hauptfächer und 5 Nebenfächer. C-Dur, a-Moll (diese beiden sind parallel), G-Dur, e-Moll (diese beiden sind parallel), D-Dur, h-Moll (diese beiden sind parallel), A-Dur, fis-Moll (diese beiden sind parallel), E-Dur, cis-Moll (diese beiden sind parallel).
Ein Programm wurde verwendet, um zufällig aus diesen 10 Skalentypen auszuwählen und Songs (Soundsequenzen) zu generieren. Zuerst haben wir eine Konstante vorbereitet, die den Regeln des Schlüssels folgt.
scale_names = ['Cmj', 'Gmj', 'Dmj', 'Amj', 'Emj', 'Amn', 'Emn', 'Bmn', 'Fsmn', 'Csmn']
cmj_set = [3, 5, 7, 8, 10, 12, 14]
cmj_base = [3, 15]
amn_base = [12, 24]
gmj_set = [3, 5, 7, 9, 10, 12, 14]
gmj_base = [10, 22]
emn_base = [7, 19]
dmj_set = [4, 5, 7, 9, 10, 12, 14]
dmj_base = [5, 17]
bmn_base = [14, 26]
amj_set = [4, 5, 7, 9, 11, 12, 14]
amj_base = [12, 24]
fsmn_base = [9, 21]
emj_set = [4, 6, 7, 9, 11, 12, 14]
emj_base = [7, 19]
csmn_base = [4, 16]
scale_names ist eine Folge von Skalen. Erstellen Sie als Nächstes eine Liste der Klangbestandteile, die aus sieben Ganzzahlen (für eine Oktave) bestehen. Stellen Sie beispielsweise den Grundton "Doremi Fasorashi" von C-Dur auf "[3, 5, 7, 8, 10, 12, 14]" ein, indem Sie sich auf die Key Map in der obigen Abbildung beziehen. Da dies eine Oktave ist, kann der eine Oktave höhere Klang als "[15, 17, 19, 20, 22, 24, 26]" berechnet werden, indem diesem Listenelement "12" hinzugefügt wird. Definieren Sie außerdem den Basisschlüssel für jede Skala. Die Basistaste von C-Dur sei eine Oktave höher "do" und "do", z. B. "cmj_base = [3, 15]".
Wie oben erwähnt, befinden sich C-Dur und A-Moll in Moll in einem parallelen Ton, und die Klänge der Bestandteile sind dieselben. Nur die Basistaste a-Moll ist definiert als "amn_base = [12, 24]" (der Klang von "la"). Wenn Sie später ein Lied (eine Sequenz) von a-Moll erzeugen, beziehen Sie sich auf die Soundliste "cmj_set" von C-Dur. Da eine solche "Parallel-Ton" -Beziehung verwendet wird, werden die folgenden Skalendefinitionskonstanten (Liste in Liste) vorbereitet und im Voraus verwendet.
scale_db = [
[cmj_set, cmj_base, amn_base],
[gmj_set, gmj_base, emn_base],
[dmj_set, dmj_base, bmn_base],
[amj_set, amj_base, fsmn_base],
[emj_set, emj_base, csmn_base]
]
Als nächstes folgt die Erzeugung des Datensatzes, der wie folgt im Pseudocode ausgedrückt wird.
# Begin
# 0 ..Generiere 9 Zufallszahlen und antworte'key'Zu entscheiden.
key_index = np.random.randint(10)
#entschieden worden sein'key'Extrahieren Sie die Liste für eine Oktave der einzelnen Noten und die Basisschlüsselliste aus.
myset, mybase = (scale_db[][], scale_db[][])
#Erweitern Sie die Skala auf 2 Oktaven.
myscale2 = prep_2x_scale(myset)
#Von Sequenzlänge'for'Schleife
for i in range(m_len):
if i == 0: #Der erste Sound ist Base Key (eine Oktave höher)
cur_key = base[1]
else: #Der zweite und die nachfolgenden Töne sind zufällig gerichtet.
direct = np.random.randint(7)
if t < 3 :
Wählen Sie in der Skalenliste eine Note aus, die um eine Note niedriger als die vorherige Note ist.
if t < 4 :
Wählen Sie den gleichen Sound wie zuvor.
else:
Wählen Sie in der Skalenliste eine Note aus, die um eins höher als die vorherige Note ist.
# Überprüfen Sie, wie die Sequenz endet
if last_ley in base: #Der letzte Ton ist Base Key?
proper = True
Als Daten übernehmen.
else
proper = False
Diese Sequenz wird abgebrochen, weil sie nicht gut endet.
# End
Auf diese Weise werden Zufallszahlen verwendet, um eine Folge von Zahlen zu erzeugen, die die Tonart des Klangs angeben. Es kann eine beliebige Sequenzlänge und eine beliebige Anzahl generiert werden. Das Ausgabebeispiel lautet wie folgt. (Diesmal beträgt die Sequenzlänge 20.)
21, 19, 17, 19, 21, 19, 17, 16, 14, 16, 17, 19, 21, 19, 21, 23, 21, 21, 19, 21, Fsmn
16, 14, 16, 14, 16, 14, 12, 11, 12, 14, 12, 14, 12, 14, 16, 14, 12, 14, 16, 16, Csmn
26, 24, 24, 24, 22, 22, 24, 22, 24, 22, 24, 22, 22, 21, 22, 24, 24, 22, 24, 26, Bmn
21, 23, 21, 19, 21, 23, 24, 24, 26, 26, 24, 23, 21, 19, 21, 19, 21, 23, 23, 21, Fsmn
24, 26, 26, 24, 22, 20, 22, 20, 19, 19, 17, 15, 14, 15, 17, 15, 17, 15, 14, 12, Amn
...
Auf diese Weise werden eine ganzzahlige Sequenz und eine Reihe von Tastenbezeichnungen (Zeichenketten) ausgegeben, aber in der Sequenz in der ersten Zeile beginnt der Bass-Sound 21 von 'fis-Moll' und endet mit demselben Sound 21. Du kannst es überprüfen. In der 'a-Moll'-Sequenz in der 5. Zeile kann auch bestätigt werden, dass sie mit dem Bass-Sound 24 beginnt und mit dem Bass-Sound 12 eine Oktave darunter endet.
Das diesmal festgelegte Problem besteht darin, Dur (helles Gefühl) und Moll (dunkles Gefühl) zu klassifizieren, aber es ist sicher, tatsächlich zu spielen und zu fragen, ob die vom obigen Programm erzeugten Daten angemessen sind. Ich denke. Ich nahm auch das iPad heraus und drückte die Tastatur mit der GarageBand-App, aber es war ziemlich schwierig zu spielen, damit ich den Ton (hell / dunkel) verstehen konnte, also gab ich sofort auf. (Vielleicht hätte ich das Midi-Standardformat verwenden und automatisch spielen sollen, aber ich habe nicht die Fähigkeiten oder den Mut dazu. Für die 10 Arten von Tonleitern, mit denen ich mich diesmal befasst habe, habe ich die Tasten gedrückt (begrenzt). Ich bestätigte jedoch, dass es hell / dunkel war.)
Bevor ich das Recurrent Neural Network (RNN) ausprobierte, untersuchte ich zunächst, was mit dem MLP-Modell (Multi-Layer Perceptron) passiert. Das Modell besteht darin, jede Folge von Tönen einer vorbestimmten Länge als unabhängige Zahl zu betrachten und diese in die Anzahl der Netzwerkeinheiten einzugeben, um eine Ausgabe zu erhalten. Da es möglich ist, zwei Hauptfächer (Dur und Moll) aus denselben Klangbestandteilen zusammenzusetzen, wurde erwartet, dass sich die Genauigkeit der Klassifizierung mit diesem Modell, das keine Sequenzen verwendet, nicht verbessern würde.
Die Konfiguration dieser Modellschicht ist wie folgt.
class HiddenLayer(object):
(Weggelassen)
class ReadOutLayer(object):
(Weggelassen)
h_layer1 = HiddenLayer(input=x, n_in=seq_len, n_out=40) #Versteckte Ebene 1
h_layer2 = HiddenLayer(input=h_layer1.output(), n_in=40, n_out=40) #Versteckte Ebene 2
o_layer = ReadOutLayer(input=h_layer2.output(), n_in=40, n_out=1) #Ausgabeschicht
Dies ist ein MLP-Modell mit zwei verborgenen Ebenen und schließlich einer Ausgabeebene für insgesamt drei Ebenen. (Dieses Mal wird die Sequenzlänge auf seq_len = 20 gesetzt.) Die folgende Abbildung zeigt den Status der mit diesem Modell durchgeführten Berechnung.
Fig. Loss & Accuracy by MLP model (RMSProp)
Die rote Linie ist die Kosten und die blaue Linie ist die Klassifizierungsgenauigkeit der Zugdaten. Es handelt sich wahrscheinlich um eine Schwingungsberechnung, weil die Einstellung der Hyperparameter (oder der Regularisierungsprozess) nicht angemessen war, die endgültige Genauigkeit jedoch 0,65 beträgt. Da es sich um ein binäres Klassifizierungsproblem handelt, beträgt die Genauigkeit 0,50, wenn Sie einen Würfel werfen oder einen Münzwurf ausführen und ihn entsprechend klassifizieren. Dies ist eine geringfügig verbesserte Genauigkeit gegenüber dieser Basislinie. Der Eindruck ist, dass es nicht so schlimm war, wie ich erwartet hatte.
Zu Beginn der Berechnung war ich besorgt über den Teil, in dem Verlust und Genauigkeit stagnierten, und den Punkt, an dem die Berechnung oszillierte. Daher ist das Ergebnis der Berechnung durch Ändern des Optimierers unten dargestellt.
Fig. Loss & Accuracy by MLP model (Gradient Descent)
Die Vibration der Berechnung ist verschwunden, aber die Stagnation zu Beginn der Berechnung bleibt bestehen. Die Genauigkeit ist leicht auf etwa 0,67 verbessert.
Als nächstes wurde die Berechnung mit Elman Net durchgeführt, dem bevorzugten und einfachen RNN (Recurrent Nueral Network). Der Hauptteil dieses Modells ist der folgende Code.
class simpleRNN(object):
# members: slen : state length
# w_x : weight of input-->hidden layer
# w_rec : weight of recurrnce
def __init__(self, slen, nx, nrec, ny):
self.len = slen
self.w_h = theano.shared(
np.asarray(np.random.uniform(-.1, .1, (nx)),
dtype=theano.config.floatX)
)
self.w_rec = theano.shared(
np.asarray(np.random.uniform(-.1, .1, (nrec)),
dtype=theano.config.floatX)
)
self.w_o = theano.shared(
np.asarray(np.random.uniform(-1., .1, (ny)),
dtype=theano.config.floatX)
)
self.b_h = theano.shared(
np.asarray(0., dtype=theano.config.floatX)
)
self.b_o = theano.shared(
np.asarray(0., dtype=theano.config.floatX)
)
def state_update(self, x_t, s0):
# this is the network updater for simpleRNN
def inner_fn(xv, s_tm1, wx, wr, wo, bh, bo):
s_t = xv * wx + s_tm1 * wr + bh
y_t = T.nnet.sigmoid(s_t * wo + bo)
return [s_t, y_t]
w_h_vec = self.w_h[0]
w_rec_vec = self.w_rec[0]
w_o = self.w_o[0]
b_h = self.b_h
b_o = self.b_o
[s_t, y_t], updates = theano.scan(fn=inner_fn,
sequences=[x_t],
outputs_info=[s0, None],
non_sequences=[w_h_vec, w_rec_vec, w_o, b_h, b_o]
)
return y_t
(Weggelassen)
net = simpleRNN(seq_len, 1, 1, 1)
y_t = net.state_update(x_t, s0)
y_hypo = y_t[-1]
prediction = y_hypo > 0.5
cross_entropy = T.nnet.binary_crossentropy(y_hypo, y_)
Erläuterungen finden Sie in der Abbildung.
Fig. Simple RNN structure
Die Abbildung zeigt die Konfiguration, die in chronologischer Reihenfolge unter der Voraussetzung der BPTT-Methode (Back Propagation through Time) entwickelt wurde. Die Tonsequenzdaten werden als [X1, X2, X3, ..., Xn] in dieses Modell eingegeben. Dies wird gewichtet und dann an die verborgene Schicht S ausgegeben, die Rekursive wird berechnet und schließlich wird die Reihe [Y1, Y2, Y3, ..., Yn] ausgegeben. Die Ausgabe der letzten Einheit Yn in dieser Y-Reihe wird durch die Aktivierungsfunktion geleitet, um eine Binärzahl (0 oder 1) zu erhalten.
Ich habe versucht, die Berechnung mit Erwartung durchzuführen, aber das Ergebnis war enttäuschend.
Fig. Loss & Accuracy by 1st RNN model (RMSProp)
Es wurden nur geringe Fortschritte erzielt, und die endgültige Genauigkeit betrug 0,58, was sich nicht wesentlich von der Nullleistung von 0,5 unterscheidet. (Es hat nicht funktioniert, selbst wenn ich es in den Optimierer geändert oder mit den Hyperparametern gespielt habe.)
Ich vermutete, dass die Ursache darin bestand, dass nur [Yn] in der Ausgabesequenz referenziert und die verbleibenden Informationen [Y1 .. Yn-1] verworfen wurden. Daher haben wir die Verbesserung des Modells untersucht.
Um auf alle Ausgabewerte der Sequenz [Y1, Y2, ..., Yn] zu verweisen, haben wir beschlossen, sie zu gewichten, um ein Signal für die Klassifizierung zu erstellen.
Fig. Simple RNN + Read-out Layer structure
Der Code wird durch Einfügen des Ausgabeschichtteils des MLP-Modells erstellt.
class simpleRNN(object):
# members: slen : state length
# w_x : weight of input-->hidden layer
# w_rec : weight of recurrnce
def __init__(self, slen, nx, nrec, ny):
(Weggelassen)
def state_update(self, x_t, s0):
(Weggelassen)
class ReadOutLayer(object): # <====Zusätzliche Klasse
def __init__(self, input, n_in, n_out):
self.input = input
w_o_np = 0.05 * (np.random.standard_normal([n_in,n_out]))
w_o = theano.shared(np.asarray(w_o_np, dtype=theano.config.floatX))
b_o = theano.shared(
np.asarray(np.zeros(n_out, dtype=theano.config.floatX))
)
self.w = w_o
self.b = b_o
self.params = [self.w, self.b]
def output(self):
linarg = T.dot(self.input, self.w) + self.b
self.output = T.nnet.sigmoid(linarg)
return self.output
(Weggelassen)
net = simpleRNN(seq_len, 1, 1, 1)
y_t = net.state_update(x_t, s0)
y_tt = T.transpose(y_t)
ro_layer = ReadOutLayer(input=y_tt, n_in=seq_len, n_out=1) # <====hinzufügen
y_hypo = (ro_layer.output()).flatten()
prediction = y_hypo > 0.5
cross_entropy = T.nnet.binary_crossentropy(y_hypo, y_)
(Weggelassen)
Die Situation, in der die Berechnung ausgeführt wurde, ist wie folgt.
Fig. Loss & Accuracy by 2nd RNN model (RMSProp)
Mit fortschreitendem Lernen verbesserte sich die endgültige Genauigkeit auf 0,73. Es wird angenommen, dass der Grund darin besteht, dass die Informationen der Ausgabesequenz wie beabsichtigt erfolgreich extrahiert wurden und der Grad an Flexibilität (Flexibilität) im Lernprozess zunahm, weil die Anzahl der Gewichte zunahm und der Freiheitsgrad des Netzwerks zunahm. ing.
Mit einer Genauigkeit von 0,73 liegt sie jedoch unter dem ursprünglich erwarteten Wert. (Ich habe über die Klassifizierungsgenauigkeit von 0,9 + als Ziel nachgedacht.) Es ist möglicherweise möglich, die Genauigkeit weiter zu verbessern, indem die Bewegung der einzelnen Gewichte untersucht und verbessert wird, aber dieses Mal möchte ich sie beenden.
Dieses Mal habe ich künstliche Musikdaten verwendet, die von einem Programm mit Zufallszahlen erstellt wurden, aber ich denke, dass dies auch die geringe Genauigkeit dieser Zeit beeinträchtigen kann. (Gibt es nicht kompliziertere Regeln in der tatsächlichen Musik?) Wenn Daten usw. erhalten werden können, möchte ich die von Menschen gemachte Melodie in Dur / Moll einteilen. (Möglicherweise müssen Sie etwas mehr über Musiktheorie lernen.)
Recommended Posts