Skip to content
Snippets Groups Projects
Select Git revision
  • e876300a595ab0213a6b12d762c504b3b2c9c990
  • main default protected
2 results

filter_images_V3.py

Blame
  • w2vMean.tex 10.41 KiB
    \newpage
    \subsection{Mean-Vektor-Klassifikationsmodell}
    %Durchschnittsvektor-Klassifkationsmodell ??
    Das Word2Vec-Modell bildet einen Vektorraum, indem ähnliche Wörter nahe 
    beieinander liegen.
    
    Es ist somit denkbar, dass Wörter, die mit einer negativen Rezension assoziiert 
    werden, ein anderes Gebiet des Vektorraumes belegen als jene, die mit positiven Rezensionen 
    assoziiert werden.
    
    Der Gedanke für dieses Modell ist es, den Mittelwert aller Wortvektoren in 
    einer Rezension zu ermitteln und somit eine Art „Satzvektor“ zu erhalten. Diese Satzvektoren 
    werden als Eingangswerte genutzt und mithilfe eines neuronalen Netzes eine 
    Klassifikation der Rezensionen durchzuführen.
    
    \subsubsection{Implementierung}
    Durch die Bildung der Satzvektoren verringert sich der benötigte Speicherbedarf, 
    somit ist es möglich, auf die Generatoren zu verzichten und die Trainingsdaten im 
    Arbeitsspeicher zu halten.
    
    Dies hilft, die benötigte Trainingszeit zu reduzieren. 
    Sollte die Arbeitsspeicherkapazität nicht ausreichen, befindet sich eine Implementierung, die Generatoren benutzt im Anhang.
    \wip{referenzieren und Tatsächlich einfügen}
    
    Um das Word2Vec-Modell zu erhalten, importieren wir die im Listing \ref{list:gw2v} geschriebene Funktion 
    \lstinline{getWordVecModel}.
    
    \begin{lstlisting}[caption={Satzvektorfunktion},label={list:mean1}]
    from gensim import utils
    from w2v_yelp_model import getWordVecModel
    import json
    
    model_path = "full_yelp_w2v_model"
    data_path ="yelp_academic_dataset_review.json"
    modelW2V = getWordVecModel(model_path)
    
    def getSentenceVector(sentence):
        split = utils.simple_preprocess(sentence)
        wordVecs = []
        for word in split:
            try:
                wordVecs.append(modelW2V.wv[word])
            except:
                pass
        if wordVecs == []:
            raise Exception('words not found in w2v model')
        return np.mean(wordVecs,axis=0)
    \end{lstlisting}
    Die Funktion \lstinline{getSentenceVector} in Zeile 8 nimmt eine Rezension entgegen und 
    teilt diese in Zeile 9 in ihre Wörter auf. Danach wird jedes Wort geprüft, 
    ob es im Word2Vec-Modell hinterlegt ist, ist dies der Fall, wird der Wortvektor der 
    Liste \lstinline{wordVecs} hinzugefügt.
    Ist kein einziges Wort der Rezension im Word2Vec-Modell enthalten, wird eine Exception geworfen.
    
    In Zeile 18 wird der Durschnitt aller Wortvektoren zurückgeben. 
    
    Um die Daten für das Klassifikationsmodell zu generieren, muss der Datensatz in die Satzvektoren transformiert werden.
    \begin{lstlisting}[caption={Daten},label={list:mean2},firstnumber=19]
    import numpy as np
    from sklearn.model_selection import train_test_split
    try:
        X = np.load("X.npy")
        Y = np.load("Y.npy")
    except:
        X = []
        Y = []
        for index, line in enumerate(open(data_path,encoding="utf8")):
            json_line = json.loads(line)
            #X Data
            try:
                X.append(getSentenceVector(json_line["text"]))
            except:
                continue
            y = float(json_line["stars"])
            if(y <3):
                Y.append(0)
            elif(y==3):
                Y.append(1)
            else:
                Y.append(2)
        X = np.array(X)
        Y = np.array(Y)
        np.save("X.npy",X)
        np.save("Y.npy",Y)
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42)
    \end{lstlisting}
    Falls die Daten bereits generiert wurden, werden diese geladen. 
    Sind keine Daten vorhanden, 
    wird der gesamte Datensatz durchlaufen und für jede einzelne Rezension der Satzvektor ermittelt. 
    
    Befindet sich kein Wort der Rezension im Word2Vec-Modell so wird diese Rezension in Zeile 33 übersprungen. 
    Die Bewertungen werden wie folg codiert: Negativ (0), Neutral (1) und Positiv (2).
     Anschließend werden die generierten Daten gespeichert, um bei mehrfacher Ausführung zeit 
     zu sparen. 
    Danach werden die Daten in Zeile 45 in Trainings- und Testdaten aufgeteilt, hierfür wird die Funktion \lstinline{train_test_split} von \lstinline{sklearn} verwendet.
    
    Die Rezensionen liegen jetzt in Form von Satzvektoren bzw. genau genommen Rezensionsvektoren vor, anhand dieser Daten kann nun ein neuronales Netz trainiert werden. 
    \begin{lstlisting}[caption={Neuronales Netz},label={list:mean3},firstnumber=46]
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense
    from tensorflow import keras
    
    modelNN = Sequential()
    modelNN.add(Dense(50,activation='relu',input_dim=X[0].size))
    modelNN.add(Dense(15,activation='relu'))
    modelNN.add(Dense(3,activation='softmax'))
    modelNN.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=["sparse_categorical_accuracy"])
    \end{lstlisting}
    Hier wurde ein dichtes mehrschichtiges neuronales Netzwerk verwendet, wobei die 
    Eingangsdimension, der Dimension der Satzvektoren (in diesem Fall 100) entspricht. 
    Bis auf die Ausgangsschicht wurde für alle Schichten des neuronalen Netzwerks 
    die Aktivierungsfunktion \lstinline{relu} verwendet. 
    Die Ausgangsschicht selbst verwendet die Aktivierungsfunktion 
    \lstinline{softmax}.
    
    In dieser Implementation wurden die hinteren Schichten, wie bei 
    Klassifikationen üblich, bewusst kleiner gewählt, um eine Verdichtung der Informationen zu erzwingen.
    
    Nachdem die Struktur des neuronalen Netzwerkes nun feststeht, muss das Netz noch kompiliert werden. Hierzu werden in 
    Zeile 54 einige Parameter gesetzt. Für den Optimierungsalgorithmus wird \lstinline{adam} gewählt. 
    Für die Fehlerfunktion, hier loss genannt, wird \lstinline{sparse_categorical_crossentropy} benutzt. 
    
    Die Fehlerfunktion \lstinline{sparse_categorical_crossentropy} ermöglicht im Gegensatz zur 
    \lstinline{binary_crossentropy} mehr als zwei Klassen zu Klassifizieren. Eine weite 
    Mögliche Fehlerfunktion ist die \lstinline{categorical_crossentropy}, hierzu müssten 
    bloß die Zielwerte \noteable{One-Hot-Encoded} werden. 
    Zuletzt wird für den Parameter \lstinline{Metrics} der Wert \lstinline{sparse_categorical_accuracy}
    übergeben.
    
    Nach dem Kompilieren des neuronalen Netzes ist jetzt das Trainieren, also das Optimieren der Gewichte, der nächste Schritt.
    
    \begin{lstlisting}[caption={Neuronales Netz - Trainieren},label={list:mean4},firstnumber=55]
    earlystop = keras.callbacks.EarlyStopping(monitor='val_sparse_categorical_accuracy',patience=10,verbose=False,restore_best_weights=True)
    cbList = [earlystop]
    count = np.unique(Y_train,return_counts=True)[1]
    cWeight = 1/(count/Y_train.size)
    hist = modelNN.fit(X_train,Y_train,epochs=1000,validation_split=0.2,batch_size=2048,class_weight={0:cWeight[0],1:cWeight[1],2:cWeight[2]},callbacks=cbList
    \end{lstlisting}
    Um ein Overfitting zu vermeiden und die Trainingszeit zu minimieren, wird in Zeile 55 
    ein \noteable{Early-Stop-Callback} definiert. 
    Der vorzeitige Stopp des Trainierens tritt ein, wenn 10 Epochen 
    lang keine Verbesserung auf der Validierungsmenge aufzuzeigen ist. 
    Hierzu wird die Metrik \lstinline{val_sparse_categorical_accuracy} überwacht. 
    Zusätzlich werden die Gewichte, die zu dem besten Ergebnis geführt haben, nach 
    dem vorzeitigen Stopp wieder hergestellt.
    
    In Zeile 59 findet das tatsächliche Training des neuronalen Netzes statt. 
    Hierfür wird der Methode \lstinline{fit} die Trainingsdaten Übergeben. Der Parameter 
    \lstinline{validation_split = 0.2} sorgt dafür das $20 \%$ der Trainingsdaten als 
    Validierungsmenge genommen werden, hierbei ist aber anzumerken das es sich bei den 
    Trainingsdaten ohnehin schon nur um $80 \%$ des Datensatzes handelt, 
    weswegen die tatsächliche Validierungsmenge $16 \%$ des Datensatzes beinhaltet.
    
    Die \lstinline{batch_size} wird aufgrund des großen Datensatzes auf 2048 gesetzt, um das Optimieren der Gewichte zu 
    beschleunigen. Eine kleinere \lstinline{batch_size} hat beim Experimentieren in disem Fall nicht zu einer schnelleren Konvergenz 
    geführt.
    
    
    Wie in Kapitel \ref{subsubsec:Dist} diskutiert gibt es verschiedene Umgänge mit unausgeglichenen Datensätzen. Die für diese Arbeit gewählte Methode der Gewichtung ist hier zu erkennen an den, an 
    die Methode \lstinline{fit} Übergebenen, Klassengewichte.
    \wip{vll noch erklären wie die Gewichte entstehen}
    \begin{lstlisting}[caption={Neuronales Netz - Evaluieren},label={list:mean5},firstnumber=60]
    modelNN.evaluate(X_test,Y_test)
    \end{lstlisting}
    Um das Modell zu evaluieren, wird in Zeile 60 die Methode evaluate mit den Testdaten aufgerufen. 
    Das hier trainierte neuronale Netzwerk klassifiziert  $80.02 \%$ der Testdaten richtig.
    \subsubsection{Konfusionsmatrix}
    Die bereits berechnete Genauigkeit ist ein gutes erstes Leistungsmerkmal des 
    Klassifikators, um den Klassifikator noch besser Einschätzen zu können, 
    lohnt es sich, eine Konfusionsmatrix aufzustellen. 
    Hierfür wird die Funktion \lstinline{confusion_matrix} von \lstinline{sklearn} benutzt.
    \begin{lstlisting}[caption={Konfusionsmatrix},label={list:mean6},firstnumber=61]
    from sklearn.metrics import confusion_matrix
    y_pred = np.argmax(modelNN.predict(X_test),axis=-1)
    confusion_matrix(Y_test,y_pred,normalize='true')
    \end{lstlisting}
    \begin{table}[ht]
        \def\arraystretch{1.3}
        \begin{center}
            \begin{tabular}{*{4}{R}}
                - &Negativ & Neutral & Positiv\\
                Negativ &0.8046 & 0.1727 & 0.0228 \\
                Neutral &0.1682 & 0.6844 & 0.1474 \\
                Positiv &0.0242 & 0.1586 & 0.8172 \\
            \end{tabular}
        \end{center}
        \caption{Konfusionsmatrix mit Klassengewichtung}
        \label{tab:m_w}
    \end{table}
    
    Wie in Tabelle \ref{tab:m_w} zu sehen ist, werden negative Bewertungen zu $80.46\%$ 
    richtig als negative Bewertung klassifiziert, $17.27\%$ werden als 
    neutral klassifiziert und $2.28\%$ werden als positiv klassifiziert.
    
    Neutrale Bewertungen werden zu $68.44\%$ richtig klassifiziert, 
    jedoch werden $16.82\%$ der neutralen Bewertungen falsch als 
    negative klassifiziert und zu $14.74\%$ falsch als positiv klassifiziert.
    
    Die positiven Bewertungen werden zu $81.72\%$ richtig als positive 
    Bewertungen klassifiziert, zu $15.86\%$ als neutral und zu 
    $2.42\%$ als negativ klassifiziert.
    
    Das gleiche Modell ohne die Gewichtung der Klassen erreicht eine Genauigkeit 
    von $85.7\%$, betrachtet man jedoch die Konfusionsmatrix in Tabelle \ref{tab:conf_no_w}, so 
    sieht man das dort bloß $27\%$ der neutralen Rezensionen richtig klassifiziert wurden.
    \begin{table}[ht]
        \def\arraystretch{1.3}
        \begin{center}
            \begin{tabular}{*{4}{R}}
                - &Negativ & Neutral & Positiv\\
                Negativ &0.8567& 0.0602& 0.0831\\
                Neutral &0.2571& 0.2708& 0.4720\\
                Positiv &0.0249& 0.0235& 0.9516\\
            \end{tabular}
        \end{center}
        \caption{Konfusionsmatrix ohne Klassengewichtung}
        \label{tab:conf_no_w}
    \end{table}