[PYTHON] Ich habe den Akkord des Songs mit word2vec vektorisiert und mit t-SNE visualisiert

Überblick

Das Lied besteht aus Akkorden, die Akkorde genannt werden. Die Reihenfolge, in der sie angeordnet sind, ist sehr wichtig und verändert die Emotionen des Songs. Ein Block mit mehreren Akkorden wird als Akkordfolge gelesen, und es gibt einen typischen Block, der beispielsweise [I-V-VIm-IIIm-IV-I-IV-V] genannt wird. ** In Bezug auf die Reihenfolge der Anordnung können Sie, wenn Sie Songs durch Sätze und Akkorde durch Wörter ersetzen, die Korrelation zwischen Akkorden sehen, indem Sie sie mit word2vec vektorisieren und mit t-SNE in zwei Dimensionen komprimieren. Ich habe die Annahme von ** überprüft. Ich habe es starr geschrieben, aber ist es nicht cool, Akkorde (Akkorde) mit ** Akkorden (Programmierung) zu analysieren? Ich wäre Ihnen dankbar, wenn Sie mit ** </ font> sympathisieren könnten.

(Dies ist eine völlige Spekulation, aber ich denke, dass es ein Meisterwerk gibt, das zusammenfasst, wie man Code programmiert, wenn man lesbaren Code programmiert, und deshalb ist das Cover eine Notiz. .)

[Klicken Sie hier für lesbaren Code] https://www.oreilly.co.jp/books/9784873115658/

Haupttechnologie

・ Schaben (Selen == 3.141.0) ・ Word2Vec (gensim == 3.7.3) ・ T-SNE (Scikit-Learn == 0,20,3)

Vorausgesetztes Wissen

・ Grundlegende Python-Grammatik

・ Verständnis des Codefortschritts (Wenn Sie nicht verstehen, überspringen Sie Kapitel 2)

Zielgruppe

・ Personen, die den Code und den Codefortschritt kennen ・ Personen, die die Code-Progression durch römische Zahlen ersetzen möchten

・ Leute, die Python studieren ・ Leute, die wissen wollen, wie man kratzt ・ Menschen, die sich für maschinelles Lernen interessieren (Verarbeitung natürlicher Sprache)

Kapitelstruktur

Ich werde es in drei Kapiteln schreiben.

** Kapitel 1: Sammeln von Daten durch Schaben mit Selen ** (ca. 100 Zeilen) ** Kapitel 2: Ersetzen des Codefortschritts durch römische Zahlen ** (ca. 150 Zeilen) ** Kapitel 3: Vektorisieren Sie den Code mit word2vec und zeigen Sie ihn mit t-SNE ** (ca. 50 Zeilen)

Wenn Sie sich auf das Schaben mit Selen beziehen möchten, lesen Sie bitte Kapitel 1, wenn Sie sich für Musik interessieren, lesen Sie bitte Kapitel 2, und wenn Sie daran interessiert sind, welche Art von Ergebnis word2vec bringen wird, lesen Sie bitte Kapitel 3.

Kommen wir nun zum Code-Inhalt.

Kapitel 1: Sammeln von Daten durch Scraping mit Selen

Das Ziel für die Datenerfassung ist diesmal die Site von U-FRET. Es gibt viele Songdaten, hohe Genauigkeit und Texte, daher denke ich, dass es eine Seite ist, die Leute, die spielen und reden, einmal benutzt haben.

[Klicken Sie hier für U-FRET] https://www.ufret.jp/

Geben Sie hier einen Künstler an und erstellen Sie einen Code, der die Akkordfolge und den Text aller Songs an csv ausgibt. (Bestimmen Sie aus dem Kontext, welche Bedeutung der Code bedeutet. Lol)

Ich denke, Selen oder schöne Suppe sind berühmt für das Schaben mit Python, Während Selenium den Treiber angibt und tatsächlich den Bildschirmübergang durchführt, um das Element abzurufen, gibt BeautyflSoup nur die URL an und ruft das Element ab. Ich denke, BeautiflSoup ist einfach zu schreiben und leicht zu verstehen, aber da meine Möglichkeiten begrenzt sind, können Elemente möglicherweise nur gezogen werden, wenn es sich um Selen handelt. Deshalb habe ich dieses Mal Selen verwendet.

scraping.py


from time import sleep
from selenium import webdriver
import chromedriver_binary
import os
import csv
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.select import Select

#Künstlereingabe
artist = "aiko"

#CSV des Songtitels ausgeben
fdir = "chord_data/" + artist + "/"
os.makedirs(fdir,exist_ok=True)
#URL für den Zugriff
TARGET_URL ='https://www.ufret.jp/search.php?key=' + artist


#Erhöhte Startgeschwindigkeit des Browsers
options = webdriver.ChromeOptions()
options.add_argument('--user-agent=hogehoge')
#Starten Sie den Browser
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(5)
driver.get(TARGET_URL)
url_list= []
urls = driver.find_elements_by_css_selector(".list-group-item.list-group-item-action")
for url in urls:
    text = url.text
    if (not "Einfacher Code für Anfänger" in text) and (not "Video Plus" in text):
        url = url.get_attribute("href")
        if type(url) is str:
            if "song." in url:
                url_list.append(url)

for url in url_list:
    #sleep(3)
    #driver.implicitly_wait(3)
    driver.get(url)
    sleep(5)
    #Wechseln Sie zum Originalschlüssel
    elem = driver.find_element_by_name('keyselect')
    select = Select(elem)
    select.select_by_value('0')
    sleep(1)
    #Songtitel holen
    title_elem = driver.find_element_by_class_name('show_name')
    title = title_elem.text
    print(title)
    sleep(1)
    #Code abrufen
    chord_list = []
    chord_elems = driver.find_elements_by_tag_name("rt")
    for chord in chord_elems:
        chord = chord.text
        chord_list.append(chord)

    #Holen Sie sich Texte
    lyric_list = []
    lyric_elems = driver.find_elements_by_class_name("chord")
    for lyric in lyric_elems:
        lyric = lyric.text.replace('\n',"")
        lyric_list.append(lyric)
    #Holen Sie sich nur die Texte
    no_chord_lyric_list = []
    no_chord_lyric_elems = driver.find_elements_by_class_name("no-chord")
    for no_chord_lyric in no_chord_lyric_elems:
        no_chord_lyric = no_chord_lyric.text
        #Bewegen Sie nur die Texte aus der Textliste nach vorne, um der Akkordfolge zu entsprechen
        idx = lyric_list.index(no_chord_lyric)
        lyric_list.remove(no_chord_lyric)
        if idx==0:
            lyric_list[0] = no_chord_lyric + lyric_list[0]
        else:
            lyric_list[idx-1] += no_chord_lyric

    #Löschen Sie den Code am Anfang jedes Textes und lassen Sie nur den Text übrig
    lyric_list = [lyric.replace(chord_list[idx],"") if chord_list[idx] in lyric else lyric for idx,lyric in enumerate(lyric_list)]
    
    #Ausgabe-Scraping-Ergebnis an csv
    fname = fdir + title + ".csv"
    with open(fname, "w", encoding="cp932") as f:
        writer = csv.writer(f)
        writer.writerow([])
        writer.writerow(chord_list)
        writer.writerow([])
        writer.writerow(lyric_list)

Hier ist der Künstler aiko. (Die Endergebnisse enthalten die Analyseergebnisse anderer Künstler.)

Wenn Sie einen Mac verwenden, werden die Entwicklertools mit Option + Befehl + i geöffnet, sodass Sie herausfinden können, wo sich das angegebene Element befindet.

Die Erfassung selbst ist einfach, da Sie nur die Zielklasse und das Tag festlegen müssen. Die Texte sind jedoch etwas kompliziert, um der Akkordfolge zu entsprechen, aber es gibt kein Problem, auch wenn Sie sie nicht verstehen.

Das Wichtigste ist, etwas Zeit in den Ruhezustand zu versetzen, um ** den Server von U-FRET nicht zu belasten und die Bearbeitungszeit für das Laden von Seiten zu berücksichtigen **. Im obigen Beispiel wird in Abständen von 5 Sekunden auf den Server zugegriffen. Trotzdem kann das Element manchmal nicht abgerufen werden. Passen Sie es daher an, indem Sie es mehrmals ausführen oder die Zeit verlängern. (Ich hatte das Gefühl, dass implizit_Warten nicht funktioniert. Ich wäre dankbar, wenn Sie mich wissen lassen könnten, wenn Sie Details haben.)

Übrigens ist das Ausgabeergebnis hier wie folgt. スクリーンショット 2020-11-01 16.51.10.png

Die CSV des Songnamens wird ausgegeben. Außerdem wird der Code-Fortschritt in der zweiten Zeile ausgegeben, und die entsprechenden Texte werden in der vierten Zeile ausgegeben. Der erste Teil ist ein Intro, daher sind keine Texte beigefügt. Machen Sie sich hier keine Sorgen, da dies die leeren Zeilen in den nächsten beiden Kapiteln ausfüllt.

Kapitel 2: Ersetzen des Codefortschritts durch römische Zahlen

Dies kann nur von jemandem verstanden werden, der mit Musik bis zu einem gewissen Grad vertraut ist. Wenn Sie sie also nicht verstehen, überspringen Sie sie. Wenn Sie mit maschinellem Lernen vertraut sind, ist es in Ordnung zu erkennen, dass ** Standardisierung als Vorverarbeitung von Daten ** durchgeführt wird. Stellen Sie es sich einfach so vor: ** Machen Sie die absolute Anzeige zu einer relativen Anzeige, damit alle Daten auf derselben Basis verarbeitet werden können **.

Um es ein wenig zu erklären, es gibt einen Hauptsound im Song, der als Tonart gelesen wird. Ich bin sicher, einige von Ihnen haben das Singen mit gedrückter Taste erlebt, weil es selbst beim Karaoke teuer ist, aber das war es auch schon. Zum Beispiel habe ich die Kanon-Progression geschrieben, die ich zu Beginn als [I-V-VIm-IIIm-IV-I-IV-V] erklärt habe, aber ich kann sie nicht spielen, weil ich die tatsächlichen Akkorde nicht kenne. Die in dieser römischen Zahl geschriebene Code-Progression erfolgt nach der Standardisierung, die wir in diesem Kapitel durchführen möchten. Tatsächlich ist es eine Progression wie [D-A-Bm-F # m-G-D-GA], die eine Kanon-Progression mit Schlüssel = D ist. Wenn der D-Code I ist, ist der A-Code V usw. Die römische Zahl gibt die relative Position auf der Tastatur an.

Nun, wie man die Tonart schätzt. ** Vergleichen Sie die diatonischen Codes aller 12 Tastenarten mit der Triade aller Akkorde des Songs und finden Sie die Tonart mit der höchsten Übereinstimmungsrate. Die Beurteilung erfolgt durch einen Algorithmus namens ** </ font>. Wenn Sie mit Musik vertraut sind, wissen Sie, was Sie meinen, richtig? Lol

Es ist also ein komplizierter Prozess, aber schauen wir uns den Code an.

scraping.py


import csv
import glob
import collections
import re
import os

artist = "aiko"
#Datenverzeichnis
fdir = "chord_data/" + artist + "/"
os.makedirs(fdir,exist_ok=True)
#Schlüsselart
key_list = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
#Römische Zahlen
rome_list = ["Ⅰ","#I","Ⅱ","#Ⅱ","Ⅲ","Ⅳ","#Ⅳ","Ⅴ","#Ⅴ","Ⅵ","#Ⅵ","Ⅶ"]
#Diatonischer Code
dtnc_chord_list_num = ["Ⅰ","Ⅱm","Ⅲm","Ⅳ","Ⅴ","Ⅵm","Ⅶm7-5"]
#Alle alle halb alle alle halb
dtnc_step = [2,2,1,2,2,2,1]
#♭#Konvertieren zu
r_dict = {'D♭':'C#', 'E♭':'D#', 'G♭':'F#','A♭':'G#','B♭':'A#'}
#Laden des Code-Fortschritts
flist = glob.glob(fdir+"*")

dtnc_chord_list_arr = []

for idx,key in enumerate(key_list):
    pos = idx
    dtnc_chord_list = []
    for num,step in enumerate(dtnc_step):
        #Grundton + Dur oder Moll
        dtnc_chord_list.append(key_list[pos]+dtnc_chord_list_num[num][1:])
        pos += step
        #Drücken Sie pos als eine Zahl von 12 oder weniger aus
        if pos >= len(key_list):
            pos -= len(key_list)
    #Speichert eine Liste mit diatonischen Codes für 12 verschiedene Tasten
    dtnc_chord_list_arr.append(dtnc_chord_list)


for fname in flist:
    with open(fname,encoding='cp932',mode='r+') as f:
        f.readline().rstrip('\n')
        chord_list = f.readline().rstrip('\n')
        f.readline().rstrip('\n')
        lyric_list = f.readline().rstrip('\n')
        #♭#Konvertieren zu
        chord_list = re.sub('({})'.format('|'.join(map(re.escape, r_dict.keys()))), lambda m: r_dict[m.group()], chord_list)
        chord_list = chord_list.split(',')
        chord_list_origin = chord_list.copy()
        # N.C.Beseitigen, abschütteln
        chord_list = [chord for chord in chord_list if "N" not in chord]
        #Nur Triade
        def get_triad(chord):
            split_chord = list(chord)
            triad = split_chord[0]
            if len(split_chord )>=2 and split_chord[1]=='#':
                triad += split_chord[1]
                if len(split_chord )>=3 and split_chord[2]=='m':
                    triad += split_chord[2]
                    if len(split_chord )>=5 and split_chord[4]=='-':
                        triad += split_chord[3]
                        triad += split_chord[4]
                        triad += split_chord[5]

            elif len(split_chord )>=2 and split_chord[1]=='m':
                triad += split_chord[1]
                if len(split_chord )>=4 and split_chord[3]=='-':
                        triad += split_chord[2]
                        triad += split_chord[3]
                        triad += split_chord[4]
            else:
                pass

            return triad
        #Wechseln Sie zur Triade
        chord_list_triad = [get_triad(chord) for chord in chord_list]
        length = len(chord_list)
        #Eindeutige Anzahl von Codes
        chord_unique = collections.Counter(chord_list_triad)
        #print(chord_unique)

        #################
        ###Bestimmen Sie den Schlüssel###
        #################

        match_cnt_arr = []
        #Berechnen Sie die Anzahl der Übereinstimmungen mit 12 Schlüsseltypen
        for dtnc_chord_list in dtnc_chord_list_arr:
            match_cnt = 0
            #Akkordieren Sie jeden der 7 diatonischen Codes_Vergleichen Sie mit dem eindeutigen Schlüsselwert,
            #Wenn es eine Übereinstimmung gibt, stimmen Sie überein_Zu cnt hinzufügen
            for dtnc_chord in dtnc_chord_list:
                if dtnc_chord in chord_unique.keys():
                    match_cnt += chord_unique[dtnc_chord]
            match_cnt_arr.append(match_cnt)
        #Maximale Anzahl von Übereinstimmungen zwischen 12 Typen
        max_cnt = max(match_cnt_arr)
        #Anzahl übereinstimmender Codes/Gesamtzahl der Codes(%)
        match_prb = int((max_cnt/length)*100)

        #Schlüsselbestimmung
        key_pos = match_cnt_arr.index(max_cnt)
        key = key_list[key_pos]
        dtnc_chord_list = dtnc_chord_list_arr[key_pos]
        file_name = os.path.basename(fname).replace('.csv','')
        print('Song Titel:{0} , key:{1} , prob:{2}'.format(file_name,key,match_prb))
        print(dtnc_chord_list)
        key_list_chromatic = key_list[key_pos:] + key_list[:key_pos]
        # key_list_chromatic.extend(key_list[key_pos:])
        # key_list_chromatic.extend(key_list[:key_pos])
        print(key_list_chromatic)

        #Konvertieren Sie Code in römische Zahlen und schreiben Sie in eine Datei
        #Funktion zum Konvertieren
        def convert_num_chord(chord_list):
            s_list = []
            n_list = []
            for idx, root in enumerate(key_list_chromatic):
                #Trennen Sie die Wurzeln nach Vorhandensein oder Nichtvorhandensein des Schärfens, um die Wurzeln zuerst durch Schärfen zu ersetzen.
                if '#' in root:
                    s_list.append([idx,root])
                else:
                    n_list.append([idx,root])
            chord_list = ['*' if "N" in chord else chord for chord in chord_list]
            for idx,root in s_list:
                chord_list = [chord.replace(root,rome_list[idx]) if root in chord else chord for chord in chord_list]
            for idx,root in n_list:
                chord_list = [chord.replace(root,rome_list[idx]) if root in chord else chord for chord in chord_list]
            chord_list = ['N.C.' if "*" in chord else chord for chord in chord_list]
            
            return chord_list

        chord_list_converted = convert_num_chord(chord_list_origin)
        print(chord_list_origin)
        print(chord_list_converted)
    with open(fname, "w", encoding="cp932") as f:
        writer = csv.writer(f)
        writer.writerow('key:{0},prob:{1}'.format(key,match_prb).split(','))
        writer.writerow(chord_list_origin)
        writer.writerow(chord_list_converted)
        writer.writerow(lyric_list.split(','))

Wie war das. Es ist lang und schwer zu verstehen, was Sie tun, aber Sie können erkennen, dass Sie es standardisiert haben. Es ist keine große Sache, also ist es sofort erledigt. Wenn ich diesen Code ausführe, erhalte ich die folgenden Ergebnisse: スクリーンショット 2020-11-01 17.43.45.png

Sie können sehen, dass der Schlüssel = D # in der ersten Zeile ist, seine Zugehörigkeitswahrscheinlichkeit 67% beträgt und der Code-Fortschritt der standardisierten römischen Zahlen in der dritten Zeile hinzugefügt wird. (Da der Code von aiko ziemlich kompliziert ist und viele nicht-diatonische Codes auftreten, beträgt die Wahrscheinlichkeit, zu diatonischen Codes zu gehören, 67%. Trotzdem kann der Schlüssel korrekt geschätzt werden, sodass die Richtigkeit dieses Prozesses korrekt ist. Ich denke du kannst es beweisen.)

Lassen Sie uns nun im letzten Kapitel diesen standardisierten Code vektorisieren und zeichnen.

Kapitel 3: Vektorisieren Sie den Code mit word2vec und zeigen Sie ihn mit t-SNE an

Lassen Sie uns nun die Daten von ungefähr 200 Songs von aiko in word2vec einfügen und vektorisieren. Die Anzahl der Dimensionen ist auf 10 festgelegt, da es höchstens 100 Arten von Ausgabecodes gibt. Für Fenster kann die Rolle des Codes mit 4 Codes vorher und nachher ausreichend beurteilt werden, daher habe ich ihn als 4 angegeben. Zusätzlich wurde sg = 0 gesetzt, um CBOW (Continuous Bag-of-Words) zu verwenden, das das zentrale Wort von der Peripherie schätzt. Dies transformiert jeden Code in einen 10-dimensionalen Vektor. Um dieses Ergebnis als Abbildung zu sehen, wurde es mit t-SNE zweidimensional komprimiert und in der Abbildung dargestellt. Die Ratlosigkeit hier ist schwierig, aber ich habe sie auf 3 gesetzt, weil die kleinere für jeden Cluster leichter zu trennen ist.

Wenn es darum geht, die Dimensionen zu komprimieren, warum nicht die Darstellungsdimension des Codes zweidimensional machen? Ich bin mir sicher, dass es Leute gibt, die das denken, aber dann gibt es fast keinen Unterschied im Code, also habe ich es 10 Dimensionen gemacht, um es auf die Ausdruckskraft zu bringen.

Schauen wir uns nun den Code an.

analyze.py


from gensim.models import Word2Vec
import glob
import itertools
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

artist = "aiko"
#Datenverzeichnis
fdir = "chord_data/" + artist + "/"
#Laden des Code-Fortschritts
flist = glob.glob(fdir+"*")
music_list = []
for fname in flist:
    with open(fname,encoding='cp932',mode='r+') as f:
        f.readline().rstrip('\n')
        f.readline().rstrip('\n')
        chord_list = f.readline().rstrip('\n').split(',')
        f.readline().rstrip('\n')
        lyric_list = f.readline()
        chord_list = [chord for chord in chord_list if "N" not in chord]
        music_list.append(chord_list)

#Code vektorisieren
model = Word2Vec(music_list,sg=0,window=4,min_count=0,iter=100,size=10)
chord_unique = list(set(itertools.chain.from_iterable(music_list)))
data = [model[chord] for chord in chord_unique]
print(model.most_similar('Ⅱ'))


#Auf 2 Dimensionen komprimieren und zeichnen
tsne = TSNE(n_components=2, random_state = 0, perplexity = 3, n_iter = 1000)
data_tsne = tsne.fit_transform(data)

fig=plt.figure(figsize=(50,25),facecolor='w')

plt.rcParams["font.size"] = 10

for i,chord in enumerate(data_tsne):
    #Punktdiagramm
    plt.plot(data_tsne[i][0], data_tsne[i][1], ms=5.0, zorder=2, marker="x",color="red")
    plt.annotate(chord_unique[i],(data_tsne[i][0], data_tsne[i][1]), size=10)
    i += 1

#plt.show()
plt.savefig("chord_data/" + artist + ".jpg ")
 

Das mit diesem Code erhaltene Ergebnis ist wie folgt. aiko.jpg

aiko hat viele Arten von Code und es ist schwierig, einen Cluster zu finden. Zum Beispiel sind in dem Kreis unten links von der Mitte die Triaden I, IV und V in der Nähe aufgereiht. Dies ist auch in der Musiktheorie richtig, und diese Liste von Akkordkombinationen ist der am häufigsten vorkommende Akkord, und aufgrund seiner Relevanz sind die Vektoren wahrscheinlich näher. Im selben Kreis gibt es einen Code namens II, der kein diatonischer Code ist, aber ich denke, dass es leicht war, in der Nähe als Doppel-Dominant vor dem Code V zu existieren. Es gibt auch # V und # VI, die häufig in der berühmten Folge von # V → # VI → I verwendet werden, und es wird angenommen, dass diese beiden Codes nahe beieinander liegen. Außerdem gibt es eine Reihe von Codes mit dem 7. im oberen Kreis, daher denke ich, dass es ein interessantes Ergebnis war, dies zu sehen.

Da es eine große Sache ist, werde ich auch die Ergebnisse anderer Künstler veröffentlichen. Erstens ist das Folgende Official Beard Man dism. Official髭男dism.jpg

Und der andere ist ein Künstler namens Andymori. Dies ist möglicherweise einfacher zu lesen, da weniger Code verwendet wird. andymori.jpg

Fazit und Zukunftsaussichten

Zusammenfassend stellte ich fest, dass das Einfügen des Codes in word2vec zu aussagekräftigen Daten führt.

Erinnern wir uns als Zukunftsperspektive im Voraus an die berühmte Akkordfolge und teilen Sie das Lied dafür durch etwa 4 Akkordfolgen. Es könnte interessant sein, eine spärliche Matrix als Akkordfolge zu erstellen, ein Thema in LDA zu erstellen und die Ähnlichkeit von Songs innerhalb eines Künstlers zu bestimmen. Darüber hinaus wäre es noch besser, wenn wir unter Künstlern Empfehlungen aussprechen könnten. Für diejenigen, die sich nur für Musik interessieren, hätte ich gerne ein Tool, mit dem Sie leicht erkennen können, wo die Änderungen durch die Arbeit in Kapitel 2 vorgenommen werden.

Wenn Sie einen Artikel schreiben, der Musik und Programmierung kombiniert, schließen Sie bitte Freunde. ..

Danke fürs Lesen.

Recommended Posts