L'autre jour, j'ai examiné le code d'un simple réseau neuronal récurrent, et de l'idée de vouloir en faire quelque chose, j'ai eu l'idée de classer la musique en majeur / mineur. .. Bien sûr, je ne suis pas familier avec la musique, alors j'ai commencé par rechercher les majeurs et les mineurs sur Wikipedia.
Tone (cho, key) est l'un des termes musicaux. Lorsqu'une mélodie ou un accord est composé en association avec une note centrale, la musique est dite tonale. Au sens étroit, dans la musique occidentale traditionnelle, il existe deux clés connues, la clé majeure et la clé mineure, qui sont composées de sons de toute l'échelle (échelle diatonique). Le son de la est le son central.
La définition de base est la suivante, mais comme je l'ai appris au primaire et au collège, la majeure est une chanson qui sonne «brillante et énergique», tandis que la mineure est une chanson qui sonne «sombre et lourde». J'ai cherché à savoir si cela pouvait être classé par programme.
La note centrale est également appelée la note fondamentale (ci-après dénommée la clé de base), mais la plus connue est le "Do majeur" avec "C" comme clé de base, le soi-disant "doremi fasolaside". (Au Japon, on l'appelle aussi "Do majeur".) Le mineur qui commence par le son de La est "A minor" ("A minor"). Je citerai ces deux échelles de wikipedia.
Fig. C major scale
Fig. A minor scale
Comprenez vous? Dans ces deux échelles, l'échelle est écrite sur une partition simple sans symbole pointu ni symbole plat sur le côté droit du symbole des aigus (Otamajakushi). En d'autres termes, do majeur et la mineur sont constitués de «sons constituants identiques». (Les éléments sont les mêmes.) La différence entre les deux est de savoir si la touche de base est "C" ou "A". (Un mineur est décalé vers le bas.) En passant, il semble que la relation entre deux gammes avec les mêmes sons constitutifs soit appelée "tonalité parallèle".
Maintenant, lorsque nous traitons de l'échelle de la musique, nous attribuons des numéros à chaque son.
Fig. Key mapping
La figure ci-dessus montre le clavier pour une octave, mais les nombres entiers tels que 3, 4, 5 ... sont attribués dans l'ordre à partir du "C" le plus à gauche (do). Les numéros sont également attribués là où il y a des touches noires, donc si vous ne regardez que les touches blanches, vous verrez une séquence de nombres légèrement irrégulière, mais nous procéderons à la programmation de cette manière.
J'ai mentionné qu'il y a des "Do majeur" et "La mineur" comme typiques en majeur et mineur, mais comme cela seul est ennuyeux en tant que problème de classification, nous traitons 5 majors, 5 mineurs, un total de 10 types d'échelles musicales. , Le problème à diviser grossièrement en majeur (majeur) et mineur (mineur) a été défini.
Nous avons préparé les 10 types d'échelles suivants. Il y a 5 majors et 5 mineurs. Do majeur, La mineur (ces deux sont parallèles), Sol majeur, Mi mineur (ces deux sont parallèles), Ré majeur, si mineur (ces deux sont parallèles), La majeur, fa dièse mineur (ces deux sont parallèles), Mi majeur, Do dièse mineur (ces deux sont parallèles).
Un programme a été utilisé pour sélectionner au hasard parmi ces 10 types de gammes et générer des morceaux (séquences sonores). Tout d'abord, nous avons préparé une constante qui suit les règles de la clé.
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 est une chaîne d'échelles. Ensuite, préparez une liste de sons constituants composée de sept entiers (pour une octave). Par exemple, réglez le son constitutif "Doremi Fasorashi" en do majeur sur [3, 5, 7, 8, 10, 12, 14]
en vous référant à la carte des touches de la figure ci-dessus. Puisqu'il s'agit d'une octave, le son une octave plus haut peut être calculé comme «[15, 17, 19, 20, 22, 24, 26]» en ajoutant «12» à cet élément de liste. Définissez également la clé de base pour chaque échelle. Soit la clé de base de do majeur "do" et "do" une octave plus haut, comme cmj_base = [3, 15]
.
Ensuite, en mineur, comme mentionné ci-dessus, do majeur et la mineur sont dans un ton parallèle, et les sons constitutifs sont les mêmes. Seule la clé de base d'un mineur est définie comme ʻamn_base = [12, 24] (le son de" la "). Plus tard, lors de la génération d'un morceau (séquence) de la mineur, reportez-vous à la liste de sons constitutifs
cmj_set` de do majeur. Etant donné qu'une telle relation de "ton parallèle" est utilisée, les constantes de définition d'échelle suivantes (liste dans la liste) sont préparées et utilisées à l'avance.
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]
]
Vient ensuite la génération de l'ensemble de données, qui est exprimé en pseudo-code comme suit.
# Begin
# 0 ..Générez 9 nombres aléatoires et répondez'key'Decider.
key_index = np.random.randint(10)
#avoir été décidé'key'Extrayez la liste pour une octave des notes constituantes et la liste des clés de base de.
myset, mybase = (scale_db[][], scale_db[][])
#Étendez l'échelle à 2 octaves.
myscale2 = prep_2x_scale(myset)
#De longueur de séquence'for'boucle
for i in range(m_len):
if i == 0: #Le premier son est Base Key (une octave plus haut)
cur_key = base[1]
else: #Le second son et les suivants sont dirigés de manière aléatoire.
direct = np.random.randint(7)
if t < 3 :
Dans la liste des échelles, sélectionnez une note inférieure d'une unité à la note précédente.
if t < 4 :
Sélectionnez le même son que précédemment.
else:
Dans la liste des échelles, sélectionnez une note supérieure de 1 à la note précédente.
# Vérifiez comment la séquence se termine
if last_ley in base: #Le dernier son est Base Key?
proper = True
Adoptez comme données.
else
proper = False
Cette séquence est abandonnée car elle ne se termine pas bien.
# End
De cette manière, des nombres aléatoires sont utilisés pour générer une séquence de nombres qui indiquent la clé du son. Toute longueur de séquence et tout nombre peuvent être générés, et l'exemple de sortie est le suivant. (Cette fois, la longueur de la séquence est de 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
...
De cette façon, une séquence d'entiers et un ensemble d'étiquettes de touches (chaînes de caractères) sont émis, mais dans la séquence de la première ligne, cela commence par le son de basse 21 de `` F sharp minor '' et se termine par le même son 21. Vous pouvez le vérifier. De plus, dans la séquence "A minor" sur la 5ème ligne, il peut être confirmé qu'elle commence par le son de basse 24 et se termine par le son de basse 12 une octave en dessous.
Le problème posé cette fois est de classer majeur (sensation lumineuse) et mineur (sensation sombre), mais il est certain de jouer réellement et de se demander si les données générées par le programme ci-dessus sont appropriées. Je pense. J'ai également sorti l'iPad et appuyé sur le clavier avec l'application GarageBand, mais c'était assez difficile à jouer pour que je puisse comprendre le ton (clair / sombre), alors j'ai abandonné immédiatement. (J'aurais peut-être dû utiliser le format standard midi et jouer automatiquement, mais je n'ai ni les compétences ni le courage de le faire. Cependant, pour les 10 types de Key Scales que j'ai traités cette fois, j'ai appuyé sur les touches (limité). Cependant, j'ai confirmé qu'il faisait clair / sombre.)
Avant d'essayer le réseau neuronal récurrent (RNN), j'ai d'abord examiné ce qui se passe avec le modèle MLP (Multi-layer Perceptron). Le modèle doit considérer chaque séquence de sons d'une longueur prédéterminée comme un nombre indépendant et entrer celui-ci dans le nombre d'unités de réseau pour obtenir une sortie. Puisqu'il est possible de composer deux majors (majeur et mineur) à partir des mêmes sons constitutifs, on s'attendait à ce que la précision de la classification ne s'améliore pas avec ce modèle qui n'utilise pas de séquences.
La configuration de cette couche de modèle est la suivante.
class HiddenLayer(object):
(Omis)
class ReadOutLayer(object):
(Omis)
h_layer1 = HiddenLayer(input=x, n_in=seq_len, n_out=40) #Couche cachée 1
h_layer2 = HiddenLayer(input=h_layer1.output(), n_in=40, n_out=40) #Couche cachée 2
o_layer = ReadOutLayer(input=h_layer2.output(), n_in=40, n_out=1) #Couche de sortie
Il s'agit d'un modèle MLP avec deux couches cachées et enfin une couche de sortie, pour un total de trois couches. (Cette fois, la longueur de la séquence est définie sur seq_len = 20.) La figure ci-dessous montre l'état du calcul effectué avec ce modèle.
Fig. Loss & Accuracy by MLP model (RMSProp)
La ligne rouge est le coût et la ligne bleue est la précision de classification des données de train. C'est un calcul vibrationnel probablement parce que le paramétrage de l'hyper (ou processus de régularisation) n'était pas approprié, mais la précision finale est de 0,65. Puisqu'il s'agit d'un problème de classification binaire, si vous lancez un dé ou effectuez un tirage au sort et le classifiez de manière appropriée, la précision est de 0,50, ce qui est une précision légèrement améliorée par rapport à cette ligne de base. L'impression est que ce n'était pas aussi grave que ce à quoi je m'attendais.
Au début du calcul, j'étais préoccupé par la partie où la perte et la précision stagnaient et le point où le calcul oscillait, donc le résultat du calcul en changeant l'optimiseur est montré dans la figure ci-dessous.
Fig. Loss & Accuracy by MLP model (Gradient Descent)
La vibration du calcul a disparu, mais la stagnation au début du calcul persiste. La précision est légèrement améliorée à environ 0,67.
Ensuite, le calcul a été effectué en utilisant Elman Net, qui est le RNN (Recurrent Nueral Network) favori et simple. La partie principale de ce modèle est le code suivant.
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
(Omis)
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_)
Reportez-vous à la figure pour obtenir des explications.
Fig. Simple RNN structure
La figure montre la configuration développée par ordre chronologique sur la base de la méthode BPTT (Back propagation through time). Les données de séquence sonore sont entrées dans ce modèle sous la forme [X1, X2, X3, ..., Xn]. Celle-ci est pondérée puis sortie vers la couche cachée S, la récursive est calculée et enfin la série [Y1, Y2, Y3, ..., Yn] est sortie. La sortie de la dernière unité Yn de cette série Y passe par la fonction d'activation pour obtenir un nombre binaire (0 ou 1).
J'ai essayé d'exécuter le calcul avec espoir, mais le résultat était décevant.
Fig. Loss & Accuracy by 1st RNN model (RMSProp)
Peu de progrès ont été réalisés et la précision finale était de 0,58, ce qui n'est pas très différent de la performance zéro de 0,5. (Cela n'a pas fonctionné même si je l'ai changé pour l'optimiseur ou joué avec les hyper paramètres.)
Je soupçonnais que la cause était que seul [Yn] dans la séquence de sortie était référencé et que les informations restantes [Y1 .. Yn-1] étaient ignorées. Par conséquent, nous avons examiné l'amélioration du modèle.
Afin de se référer à toutes les valeurs de sortie de la séquence [Y1, Y2, ..., Yn], nous avons décidé de les pondérer pour créer un signal de classification.
Fig. Simple RNN + Read-out Layer structure
Le code est créé en insérant la partie couche de sortie du modèle MLP.
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):
(Omis)
def state_update(self, x_t, s0):
(Omis)
class ReadOutLayer(object): # <====Classe supplémentaire
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
(Omis)
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) # <====ajouter à
y_hypo = (ro_layer.output()).flatten()
prediction = y_hypo > 0.5
cross_entropy = T.nnet.binary_crossentropy(y_hypo, y_)
(Omis)
La situation dans laquelle le calcul a été exécuté est la suivante.
Fig. Loss & Accuracy by 2nd RNN model (RMSProp)
Au fur et à mesure que l'apprentissage progressait, la précision finale s'est améliorée à 0,73. On pense que la raison est que les informations de la séquence de sortie ont été extraites avec succès comme prévu et que le degré de flexibilité (flexibilité) dans le processus d'apprentissage a augmenté parce que le nombre de poids a augmenté et le degré de liberté du réseau a augmenté. ing.
Cependant, avec une précision de 0,73, elle est inférieure à la valeur initialement attendue. (Je pensais à la précision de classification de 0,9 + comme cible.) Il est peut-être possible d'améliorer encore la précision en étudiant et en améliorant le mouvement de chaque poids, mais cette fois, j'aimerais le terminer.
Cette fois, j'ai utilisé des données musicales artificielles créées par un programme avec des nombres aléatoires, mais je pense que cela peut aussi affecter la faible précision de cette heure. (N'y a-t-il pas des règles plus compliquées dans la musique réelle?) Si des données, etc. peuvent être obtenues, je voudrais classer la mélodie faite par les humains en majeure / mineure. (Vous devrez peut-être étudier un peu plus le solfège.)
Recommended Posts