La música no es solo un arte, la música es una expresión de lo humano. condición. Cuando un artista está haciendo una canción, a menudo puedes escuchar las emociones, experiencias y energía que tienen en ese momento. La música conecta a las personas de todo el mundo y se comparte entre culturas.
Entonces, ¿no hay forma de que una computadora pueda competir con esto, verdad? Esa es la pregunta que mi grupo y yo preguntamos cuando elegimos nuestro proyecto semestral para nuestra clase de aprendizaje automático.
Nuestro objetivo era crear algo que hiciera creer al oyente que lo que escuchaban fue creado por un humano. Creo que tuvimos éxito personalmente, pero te dejaré ser el juez.
Enfoque
Para crear música, necesitábamos alguna forma de aprender los patrones y comportamientos de las canciones existentes para poder reproducir algo que sonaba como musica actual.Todos nosotros habíamos estado interesados en el aprendizaje profundo, por lo que vimos esta oportunidad perfecta para explorar esta tecnología.
Para comenzar, investigamos las soluciones existentes para este problema y encontramos un excelente tutorial de Sigurður Skúli en sobre cómo generar música con Keras . Después de leer su tutorial, tuvimos una idea bastante buena de lo que queríamos hacer.
El formato del archivo es importante ya que es lo que decidiría cómo abordaríamos el problema. El tutorial usó archivos midi, así que seguimos su ejemplo y decidimos usarlos también porque eran fáciles de analizar y aprender (puedes aprender más sobre ellos aquí ). El uso de archivos midi nos dio un par de ventajas porque pudimos detectar fácilmente el tono de una nota y la duración.
Pero antes de adentrarnos y comenzar a construir nuestra red, necesitábamos más información sobre cómo está estructurada la música y los patrones a considerar. Para esto fuimos a un buen amigo mío, Mitch Burdick. Nos ayudó a determinar algunas cosas acerca de nuestro enfoque y nos dio un curso intensivo sobre teoría musical simple.
Después de nuestra conversación, nos dimos cuenta de que el paso del tiempo y la duración de la secuencia serían dos factores importantes para nuestra red.
El paso del tiempo se determinó cuando analizamos y producimos cada nota, mientras que la longitud de la secuencia determinó cómo aprendimos los patrones en una canción. Para nuestra solución, elegimos un paso de tiempo de 0.25 segundos y 8 notas por paso de tiempo.
Esto correspondía a una signatura de tiempo de 4/4, que para nosotros significaba 8 secuencias diferentes de 4 notas. Al aprender estas secuencias y repetirlas, podríamos generar un patrón que suena como música real y construir desde allí. Como punto de partida, utilizamos el código mencionado en el tutorial de Skúli, sin embargo, al final, nuestra implementación diferenció de la original de varias maneras:
- Arquitectura de red
- Restringida a una sola tecla
- Uso de notas y descansos de longitud variable [19659008] Uso de la estructura / patrones de una canción
Arquitectura de red
Para nuestra arquitectura, decidimos basarnos en las capas de memoria bidireccional a largo plazo (BLSTM). A continuación se muestra el código Keras que usamos:
model = Sequential () model.add ( Bidirectional ( LSTM (512, return_sequences = True), input_shape = ( network_input .shape [1]network_input.shape [2]), ) ) model.add (Dropout (0.3)) model.add (Bidirectional (LSTM (512))) model.add (Dense (n_vocab)) model.add (Activation ("softmax")) model.compile (loss = "categorical_crossentropy", optimizer = "rmsprop")
Nuestros pensamientos atrás esto fue usando las notas antes y después de un punto en particular en una canción que podríamos generar melodías que sonaban similares a las de un humano. A menudo, cuando se escucha música, lo que vino antes ayuda al oyente a predecir lo que sigue.
Ha habido muchas ocasiones en las que he estado escuchando una canción y puedo moverme a un ritmo en particular porque puedo predecir lo que vendrá después. Esto es exactamente lo que sucede cuando se acumula una gota en una canción.
La canción se vuelve cada vez más intensa, lo que hace que el oyente aumente la tensión en anticipación a la caída y provoca ese momento de alivio y emoción cuando finalmente llega. Aprovechando esto, pudimos producir ritmos que sonarían de manera natural y producirían las mismas emociones que nos hemos acostumbrado a esperar en la música moderna.
Para el número de nodos en nuestras capas BLSTM, elegimos 512 como estaba Lo que Skúli usó. Sin embargo, experimentamos un poco con esto, pero debido a limitaciones de tiempo terminamos quedándonos con el número original. Lo mismo ocurre con la tasa de deserción del 30% (lea más sobre las tasas de deserción aquí ).
Para la función de activación, elegimos softmax y para nuestra función de pérdida, elegimos la entropía cruzada categórica ya que funcionan bien para problemas de clasificación de múltiples clases, como la predicción de notas (puede leer más sobre ambos aquí ). Finalmente, elegimos RMSprop para nuestro optimizador, ya que Keras recomendó esto para RNN.
Restricción de clave
Suponemos que asumimos que solo usaríamos canciones de la misma clave: Do mayor / A menor. La razón de esto es que manteniendo todas las canciones que producimos en la misma clave, nuestra salida sonará más parecida a una canción, ya que la red nunca aprendería notas que harían que una canción se salga de la tecla.
Para hacer esto usamos un script que encontramos aquí de Nick Kelly . Esta parte fue realmente simple pero nos dio una gran mejora en nuestros resultados.
Notas y descansos de longitud variable
Una parte importante de la música es el uso dinámico y creativo de notas y descansos de longitud variable. Esa única nota larga del guitarrista seguida de una pausa pacífica puede enviar una ola de emoción al oyente a medida que escuchamos el corazón y el alma del jugador derramado en el mundo.
Para capturar esto, buscamos formas de introducir notas largas, notas cortas y descansos para poder crear diferentes emociones a lo largo de la canción.
Para implementar esto, observamos el tono y la duración de una nota y tratamos esto como Un valor separado que podríamos ingresar en nuestra red.
Esto significó que un C # jugado durante 0.5 segundos y un C # jugado durante 1 segundo serían tratados como valores diferentes por la red.
Esto nos permitió aprender qué tonos se tocaron más largos o más cortos que otros y nos permitió combinar notas para producir algo que sonara natural y apropiado para esa parte de la canción.
Por supuesto, los descansos no se pueden olvidar ya que son cruciales para guiarlos. El oyente a un lugar de anticipación o emoción.
Una nota lenta y una pausa seguidas de un estallido de notas de disparo rápido pueden crear una emoción diferente a la de varias notas largas con largas pausas entre ellas.
Sentimos que esto era importante para replicar la experiencia que tiene el oyente al escuchar una canción relajante el domingo por la tarde o un himno de la fiesta del viernes por la noche.
Para lograr estos objetivos, tuvimos que centrarnos en nuestro preprocesamiento. Nuevamente aquí, comenzamos con el código del tutorial de Skúli y lo adaptamos a nuestras necesidades.
para element in notes_to_parse: if (isinstance (element, note.Note) o isinstance (element, chord.Chord ): duration = element.duration.quarterLength if isinstance (element, note.Note): name = element.pitch elif isinstance (element, chord.Chord): nombre = ".". Join (str (n) para n en element.normalOrder) notes.append (f "{name} $ {duration}")
rest_notes = int ((element.offset - prev_offset) / TIMESTEP - 1) para _ en rango (0, rest_notes): notes.append ("NULL")
prev_offset = element.offset
Para detallar el código de arriba, creamos notas combinando su tono y duración con un “$” para alimentar nuestra red. Por ejemplo, “A $ 1.0”, “A $ 0.75”, “B $ 0.25”, etc. Para ser utilizados por nuestra red (las entradas se codifican al asignar cada nota / duración única a un número entero y luego se dividen todos los enteros por el número de combinaciones únicas que codifican cada una como un número de punto flotante entre 0 y 1).
La parte más interesante es calcular cuántos descansos se deben insertar. Miramos el desplazamiento de la nota actual y lo comparamos con el desplazamiento de la última nota que vimos.
Tomamos esta brecha y la dividimos por nuestro paso en el tiempo para calcular cuántas notas de descanso podemos ajustar (menos 1 porque realmente esto calcula cuántas notas caben en la brecha, pero una de ellas es nuestra siguiente nota real, así que no queremos doble contarlo).
Un ejemplo sería si una nota comenzara a los 0.5s y la siguiente no comenzara hasta los 1.0s. Con un paso de tiempo de 0.25 (cada nota se toca en intervalos de 0.25 s), esto significaría que necesitamos una nota de descanso para llenar el espacio.
Estructura de la canción
Por último, una de las partes más importantes de escribir una canción es La estructura, y esta es una de las cosas que encontramos carentes de soluciones existentes. En mi opinión, la mayoría de los investigadores esperan que su red aprenda esto a través de su red, y no creo que sea un enfoque equivocado. Sin embargo, creo que esto introduce complejidad en el problema y conduce a una mayor dificultad. Esto podría ser una fuente de mejora en nuestra solución, ya que adoptamos un enfoque más manual y asumimos un patrón constante.
Uno de los supuestos clave que hicimos es que solo produciríamos canciones que sigan el patrón específico ABCBDB donde :
- A es el primer verso
- B es el coro
- C es el segundo verso
- y D es el puente
Inicialmente, probamos ABABCB, pero parecía demasiado formulable. Para resolver esto, decidimos introducir un segundo verso que era diferente al primero pero que aún estaba relacionado. Generamos el primer verso a partir de una nota aleatoria y luego generamos el segundo verso basado en el primero. Efectivamente, esto está generando una sola sección que es el doble de larga y se divide en dos. El proceso de pensamiento aquí fue que si creamos un verso, el segundo debería ajustarse a la misma vibra, y al usar el primero como referencia podríamos lograr esto.
def genera_notes (self, model, network_input, pitchnames, n_vocab): "" "Genera notas desde la red neuronal basándose en una secuencia de notas" "" int_to_note = dict ( ( número + 1, nota ) para el número, anote en enumerar (nombres de tono) ) int_to_note [0] = "NULL"
def get_start (): # elija una secuencia aleatoria de la entrada como punto de partida para [1945902020] # la predicción inicio = numpy.random.randint (0, len (network_input) - 1) patrón = network_input [start] prediction_output = [] patrón de retorno, prediction_output
# generar verso 1 verse1_pattern, verse1_prediction_output = get_start () para no e_index en el rango (4 * SEQUENCE_LEN): prediction_input = numpy.reshape ( verse1_pattern, (1, len (verse1_pattern), 1) ) prediction_input = prediction_input / float (nvv) ] prediction = model.predict (prediction_input, verbose = 0)
index = numpy.argmax (prediction) result = int_to_note [index] verse1_prediction_output.append (result)
verse1_pattern.append (index)
] verse1_pattern = verse1_pattern [1 : len(verse1_pattern)]
# genera el verso 2 verse2_pattern = verse1_pattern verse2_prediction_mentput = [index]] la caja de luz de la tabla de la fiesta de la ciudad de los animales de compañía en cada momento. verse2_pattern, (1, len (verse2_pattern), 1) ) prediction_input = prediction_input / float (n_vocab)
prediction = model.predict (prediction_input, verbose = 0)
(predicción) resultado = int_to_note [19659042] verse2_prediction_output.append (result)
verse2_pattern.append (index) verse2_pattern = verse2_pattern [1 : len(verse2_pattern)] [1 : len(verse1_pattern)]
esparcimiento de personas en las áreas de juego en la red de parques de animales. 4 * SEQUENCE_LEN): prediction_input = numpy.reshape ( chorus_pattern, (1, len (chorus_pattern), 1) ) prediction_input = prediction_input / float (n_vvocab) (19249020)] .predict (prediction_input, verbose = 0)
index = numpy.argmax (prediction) result = int_to_note [index] chorus_prediction_output.append (chuzhalo) [1 : len(chorus_pattern)]
# generar puente bridge_pattern, bridge_prediction_output = get_start () para note_index en el rango (4 * SEQUENCE_LEN): prediction_input = numpy.reshape ( ) (bridge_pattern), 1) ) prediction_input = prediction_input / float (n_vocab)
prediction = model.predict (prediction_input, verbose = 0)
index = numpy.argmax (prediction) result = int_to_note ] + bridge_prediction_output + chorus_prediction_output )
Resultados
Pudimos lograr resultados sorprendentes con este enfoque. Podríamos generar constantemente canciones únicas que caen en el género apropiado en el que entrenamos las redes respectivas. A continuación, se incluyen algunos ejemplos de salidas de nuestras diversas redes.
https://medium.com/media/4f03a10e1e54713457253e8eaa7c1d8c/href https://med52ff6php.pc3pb2c215b6ca8c36e62916b2e3f52ff6phpcpPp.p.pdf3c215b6ca8c36e62916b2e3f52ff6phprepezpac .com / media / 48e16852769f5c97d530c2d9766f552a / href
Conclusión
La generación de música por máquinas es de hecho posible. ¿Es mejor o podría ser mejor que la música generada por los humanos? Sólo el tiempo dirá. A pesar de estos resultados, diría que es definitivamente posible.
Trabajo Futuro
Se podrían hacer varias mejoras que acercarían esto aún más a la verdadera música. Algunas ideas / experimentos posibles incluyen:
- Aprenda patrones en canciones en lugar de juntar partes manualmente
- Tome nota de la duración como una entrada independiente a la red en lugar de tratar cada tono / duración por separado
- Amplíe a múltiples instrumentos Aléjese de los archivos midi y produzca / aprenda de archivos MP3 reales
- Aprenda el paso de tiempo, la longitud de la secuencia y la signatura de tiempo
- Introduzca la aleatoriedad para emular el “error humano / experimentación”
- Permita varias teclas
- Aprenda cómo usar intros y outros
Agradecimientos
Me gustaría agradecer a mis compañeros de equipo Izaak Sulka y Jeff Greene por su ayuda en este proyecto, así como a mi amigo Mitch Burdick por su experiencia en música que nos permitió obtenerlos. Buenos resultados. Y, por supuesto, nos gustaría agradecer a Sigurður Skúli por su tutorial, ya que nos dio un gran punto de partida y algo de referencia. Por último, pero no menos importante, me gustaría agradecer a Nick Kelly por su guión para transponer canciones a Do mayor.
El código para este proyecto se puede encontrar aquí: https://github.com/tylerdoll/music-generator
Descargo de responsabilidad: la música utilizada en nuestro proyecto no nos pertenece y se obtuvo de varios sitios web públicos.
Making Music with Machine Learning se publicó originalmente en Hacia la ciencia de datos en Medio, donde las personas continúan la conversación resaltando y respondiendo a esta historia.