[PYTHON] Vorhersage kurzlebiger Arbeiten von Weekly Shonen Jump durch maschinelles Lernen (Teil 2: Lernen und Bewertung)

1. Zuallererst

Dieser Artikel ist eine Fortsetzung von Vorhersage kurzlebiger Arbeiten von Weekly Shonen Jump durch maschinelles Lernen (Teil 1: Datenanalyse). Anhand der im ersten Teil erfassten Daten werden wir einen Klassifikator mit einem mehrschichtigen Perzeptron implementieren und bewerten. Im Folgenden bezieht sich der Sprung auf den wöchentlichen Jungensprung.

result.png

Die obige Abbildung ist Teil des Bewertungsergebnisses. Bei Verwendung des besten Modells (Gefiltert + Erweitert) ** Wenn Sie die Veröffentlichungsreihenfolge [^ Veröffentlichungsreihenfolge] bis zur 7. Woche und die Anzahl der Farben eingeben, besteht eine Wahrscheinlichkeit von 65%, dass die Arbeit innerhalb von 20 Wochen abgeschlossen wird. Es stellte sich als vorhersehbar heraus ** [^ Sprung]. Die letzten 100 Werke, die in der Kulturagentur Media Arts Database registriert sind, wurden zur Bewertung verwendet, und andere Werke wurden zum Lernen und zur Parameteranpassung verwendet. .. Ich habe mir verschiedene Dinge ausgedacht, aber diese Leistung war die Grenze mit meiner eigenen Kraft. Die Details werden unten erklärt. Das Jupyter-Notizbuch ist hier, der Quellcode ist hier -comic-end).

Dieser Artikel gibt keine Meinung zu den redaktionellen Richtlinien von Jump ab und appelliert nicht an das unangemessene Ende oder die Fortsetzung von Arbeiten. Viel Glück springen! Viel Glück Manga-Künstler!

[^ Posting order]: Die Jump-Redaktion scheint das Prinzip der Vorherrschaft von Fragebögen geleugnet zu haben und sagte: "Wir berücksichtigen nicht unbedingt nur die Ergebnisse von Leser-Fragebögen." Die Redaktion "Jump" bestreitet Gerüchte über das oberste Prinzip des Fragebogens ... Leser sind kompliziert

[^ Jump]: Wie oben erwähnt, entscheidet die Jump-Redaktion in der Realität unter Berücksichtigung verschiedener Faktoren über die eingestellte Arbeit. Ich hoffe, Sie verstehen diesen Artikel als Täuschung eines Sprungfans.

2. Umweltbau

2.1 anaconda Erstellen Sie die folgende virtuelle Umgebung "comic" mit "anaconda".

conda create -n comic python=3.5
source activate comic
conda install pandas matplotlib jupyter notebook scipy scikit-learn seaborn scrapy
pip install tensorflow

Die yml-Datei befindet sich hier. tensorflow und scikit-learn sind enthalten. Da ich im ersten Teil pairplot () verwendet habe, seaborn /) Wird eingefügt.

2.2 Informationen zum Inhaltsverzeichnis

Es wird angenommen, dass sich das in Teil 1 erhaltene wj-api.json im Verzeichnis data befindet. Nehmen Sie außerdem an, dass der in Teil 1 eingeführte "ComicAnalyzer" in "comic.py" definiert ist.


import comic

wj = comic.ComicAnalyzer()

2.3 Modul

Ich möchte den Titel des Cartoons auf Japanisch anzeigen, also verweise auf Japanisch mit Matplotlib unter Ubuntu zeichnen. Wenn Sie ein anderes als Ubuntu verwenden, ergreifen Sie bitte die entsprechenden Maßnahmen.

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf

import matplotlib
from matplotlib.font_manager import FontProperties
font_path = '/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf'
font_prop = FontProperties(fname=font_path)
matplotlib.rcParams['font.family'] = font_prop.get_name()

3. Modell

3.1 Problemstellung

In diesem Artikel werden wir das Problem der Klassifizierung, ob es sich um eine kurzlebige Arbeit handelt oder nicht, anhand der folgenden Eingaben herausfordern.

Eingang

Als Eingabe verwenden wir in jeder Woche bis zur 7. Woche der Serialisierung und der Gesamtzahl der Farben insgesamt 8 Informationsdimensionen in der Reihenfolge der Veröffentlichung. Der Grund für die Verwendung der Daten bis zur 7. Woche ist, dass ich die Beendigung der kürzesten Serialisierung (8 Wochen) in den letzten Jahren, spätestens eine Woche zuvor, vorhersagen wollte. Der Grund für die Verwendung der Anzahl der Farben sowie der Reihenfolge der Veröffentlichung ist die Verbesserung der Vorhersagegenauigkeit. Intuitiv haben die populäreren Werke tendenziell mehr Farben.

Kurzlebige Arbeit

In Teil 1 werden "kurzlebige Werke" wie folgt definiert.

In diesem Artikel verwenden wir maschinelles Lernen, um kurzlebige Arbeiten vorherzusagen (Arbeiten, die innerhalb von 10 Wochen abgeschlossen sein werden).

Als vorläufiges Experiment habe ich versucht, kurzlebige Werke mit dieser Definition zu klassifizieren, aber ich habe nicht gut gelernt. Wenn wir wj-api.json noch einmal analysieren, können wir sehen, dass nur sehr wenige Arbeiten innerhalb von 10 Wochen abgeschlossen sind.

cdf.png

Die Abbildung links zeigt die kumulative Verteilung aller Werke, und die Abbildung rechts konzentriert sich auf bis zu 50 Wochen in der Abbildung links. Die horizontale Achse ist der Veröffentlichungszeitraum und die vertikale Achse ist das Verhältnis. Aus der Abbildung rechts ist ersichtlich, dass weniger als 10% der Arbeiten innerhalb von 10 Wochen abgeschlossen waren. Warum neuronale Netze SVM nicht schlagen können Wie Sie bereits betont haben, ist Multilayer Perceptron nicht gut darin, unausgeglichene Daten zu lernen [^ Commitment] ..

Laut Anwenden von Deep Learning auf reale Probleme - Merantix, wenn das Datenetikett voreingenommen ist Eine Änderung der Kennzeichnung wurde als eine der Gegenmaßnahmen vorgeschlagen. Aus diesem Grund wird in diesem Artikel der Einfachheit halber die Definition von kurzlebigen Arbeiten in ** Arbeiten geändert, die innerhalb von 20 Wochen abgeschlossen wurden ** (für die Vorhersage von Arbeiten, die innerhalb von 10 Wochen abgeschlossen wurden, lassen Sie sie bitte als zukünftige Hausaufgabe ...). Wenn der Schwellenwert auf 20 Wochen festgelegt ist, kann etwa die Hälfte der Arbeiten als kurzlebige Arbeit behandelt werden.

[^ Commitment]: Dann ist es vernünftig darauf hinzuweisen, dass Sie SVM verwenden sollten. Dieses Mal war ich besonders an Perceptron interessiert, um zu studieren.

3.2 Mehrschichtiges Perzeptron

Das Folgende ist ein Modell von Multilayer Perceptron, das in diesem Artikel verwendet wird. Weitere Informationen zu mehrschichtigem Perzeptron finden Sie unter Hinweise zur Backpropagation-Methode.

model.png

Die verborgene Ebene besteht aus 7 Knoten und 2 Ebenen. Als versteckte Schichtaktivierungsfunktion [ReLU](https://ja.wikipedia.org/wiki/%E6%B4%BB%E6%80%A7%E5%8C%96%E9%96%A2%E6 % 95% B0 # ReLU.EF.BC.88.E3.83.A9.E3.83.B3.E3.83.97.E9.96.A2.E6.95.B0.EF.BC.89) .. Die Ausgabeebene gibt die Wahrscheinlichkeit aus, dass es sich um eine kurzlebige Arbeit handelt, und als Aktivierungsfunktion [Sigmoid](https://ja.wikipedia.org/wiki/%E6%B4%BB%E6%80%A7%E5%8C%96 % E9% 96% A2% E6% 95% B0 # .E3.82.B7.E3.82.B0.E3.83.A2.E3.82.A4.E3.83.89.E9.96.A2.E6. Verwenden Sie 95.B0). Verwenden Sie Adam zum Lernen. Die Lernrate $ r $ wird mit TensorBoard angepasst. Übrigens wird für das obige Modell (Anzahl der verborgenen Schichten, Anzahl der verborgenen Schichten, Aktivierungsfunktion der verborgenen Schichten, Optimierungsalgorithmus) das Modell mit der besten Leistung im vorläufigen Experiment ausgewählt.

3.3 Datensatz

In diesem Artikel werden 273 kurzlebige Werke und 273 andere Werke (im Folgenden als kontinuierliche Werke bezeichnet) für insgesamt 546 Werke verwendet. Aus neuen Arbeiten werden 100 Arbeiten als Testdaten, 100 Arbeiten als Validierungsdaten und 346 Arbeiten als Trainingsdaten verwendet. Die Testdaten sind die Daten für die endgültige Bewertung, die Validierungsdaten sind die Daten für die Anpassung der Hyperparameter und die Trainingsdaten sind die Daten für das Training. Für diese Warum müssen wir den Validierungssatz und den Testsatz für überwachtes Lernen trennen? wird detailliert beschrieben.

In diesem Artikel werden wir Trainingsdaten auf die folgenden drei verschiedenen Arten verwenden. "x_test" und "y_test" repräsentieren Testdaten, "x_val" und "y_val" repräsentieren Validierungsdaten und "x_tra" und "y_tra" repräsentieren Trainingsdaten.

dataset.png

In Datensatz 1 werden alle 346 Trainingsdaten zum Lernen verwendet. Datensatz 2 schließt etwa die Hälfte der alten Werke aus den Trainingsdaten aus und verwendet sie zum Lernen. Dies liegt daran, dass ich dachte, dass einige der Trainingsdaten zu alt sind, um die aktuelle Cutoff-Richtlinie der Sprungredaktion zu lernen (Lärm werden). In Datensatz 3 wird Datensatz 2 durch Datensatzerweiterung aufgeblasen und zum Lernen verwendet. Dies liegt daran, dass wir der Meinung waren, dass Datensatz 2 zu wenig Trainingsdaten enthält, um eine ausreichende Generalisierungsleistung bereitzustellen.

Die Datensatzerweiterung ist eine Technik zum Verarbeiten von Daten zum Aufblasen von Trainingsdaten. Es ist bekannt, dass es hauptsächlich bei der Verbesserung der Leistung der Bilderkennung und Spracherkennung wirksam ist. Weitere Informationen finden Sie in Abschnitt 7.4 des Deep Learning-Buches und in So erhöhen Sie die Anzahl der Bilder in einem Datensatz für maschinelles Lernen. Bitte beziehen Sie sich auf / bohemian916 / items / 9630661cd5292240f8c7). Das Thema hinter diesem Artikel ist die Bewertung der Wirksamkeit der Datensatzerweiterung bei der Vorhersage der Beendigung wöchentlicher Comic-Magazine. In diesem Artikel wird die Datenerweiterung mit der unten gezeigten Methode durchgeführt.

aug.png

Grob gesagt werden neue Daten generiert, indem zwei Daten mit derselben Bezeichnung zufällig ausgewählt und ein zufällig gewichteter Durchschnitt daraus erstellt werden. Dahinter steht die Annahme, dass Arbeiten mit mittleren Noten (in der Reihenfolge ihrer Veröffentlichung) mehrerer kurzlebiger Werke auch kurzlebige Werke sind. Intuitiv scheint es eine nicht so schlechte Annahme zu sein.

4. Implementierung

Die Klasse "ComicNet ()" zum Verwalten des mehrschichtigen Perzeptrons ist unten definiert. ComicNet () legt verschiedene Daten (Test, Validierung und Zug) fest, erstellt ein mehrschichtiges Perzeptron, trainiert und testet. TensorFlow wird für die Implementierung verwendet. In Bezug auf TensorFlow bin ich kein Programmierer oder Datenwissenschaftler, aber ich habe Tensorflow einen Monat lang berührt, daher ist es sehr einfach zu verstehen / items / c977c79b76c5979874e8) ist detailliert.

ComicNet()


class ComicNet():
    """Diese Klasse verwaltet ein mehrschichtiges Perzeptron, das angibt, ob eine Manga-Arbeit von kurzer Dauer ist oder nicht.
    :param thresh_Woche: Schwelle, die kurzlebige Werke von anderen trennt.
    :param n_x: Anzahl der Veröffentlichungsaufträge, die in das mehrschichtige Perzeptron eingegeben werden sollen.
    """
    def __init__(self, thresh_week=20, n_x=7):
        self.n_x = n_x
        self.thresh_week = thresh_week        

Das Folgende ist eine kurze Beschreibung jeder Mitgliedsfunktion.

4.1 Datensatzeinstellungen: configure_dataset () usw.

ComicNet


    def get_x(self, analyzer, title):
        """Es ist eine Funktion, um die normalisierte Veröffentlichungsreihenfolge der angegebenen Arbeit bis zur angegebenen Woche abzurufen."""
        worsts = np.array(analyzer.extract_item(title)[:self.n_x])
        bests = np.array(analyzer.extract_item(title, 'best')[:self.n_x])
        bests_normalized = bests / (worsts + bests - 1)
        color = sum(analyzer.extract_item(title, 'color')[:self.n_x]
                    ) /self.n_x
        return np.append(bests_normalized, color)

    def get_y(self, analyzer, title, thresh_week):
        """Eine Funktion, um festzustellen, ob es sich bei der angegebenen Arbeit um eine kurzlebige Arbeit handelt."""
        return int(len(analyzer.extract_item(title)) <=  thresh_week)

    def get_xs_ys(self, analyzer, titles, thresh_week):
        """Eine Funktion, die die Features, die Bezeichnung und den Titel der angegebenen Arbeitsgruppe zurückgibt.
          y==0 und y==Die Anzahl der Daten von 1 wird ausgerichtet und zurückgegeben.
        """
        xs = np.array([self.get_x(analyzer, title) for title in titles])
        ys = np.array([[self.get_y(analyzer, title, thresh_week)] 
                       for title in titles])
        
        # ys==0 und ys==Richten Sie die Anzahl der Daten auf 1 aus.
        idx_ps = np.where(ys.reshape((-1)) == 1)[0]
        idx_ng = np.where(ys.reshape((-1)) == 0)[0]
        len_data = min(len(idx_ps), len(idx_ng))
        x_ps = xs[idx_ps[-len_data:]]
        x_ng = xs[idx_ng[-len_data:]]
        y_ps = ys[idx_ps[-len_data:]]
        y_ng = ys[idx_ng[-len_data:]]
        t_ps = [titles[ii] for ii in idx_ps[-len_data:]]
        t_ng = [titles[ii] for ii in idx_ng[-len_data:]]
        
        return x_ps, x_ng, y_ps, y_ng, t_ps, t_ng
        
    def augment_x(self, x, n_aug):
        """Eine Funktion, die künstlich eine bestimmte Anzahl von x-Daten generiert."""
        if n_aug:
            x_pair = np.array(
                [[x[idx] for idx in 
                  np.random.choice(range(len(x)), 2, replace=False)]
                 for _ in range(n_aug)])
            weights = np.random.rand(n_aug, 1, self.n_x + 1)
            weights = np.concatenate((weights, 1 - weights), axis=1)
            x_aug = (x_pair * weights).sum(axis=1)
            
            return np.concatenate((x, x_aug), axis=0)
        else:
            return x
        
    def augment_y(self, y, n_aug):
        """Eine Funktion, die künstlich eine bestimmte Anzahl von y-Daten generiert."""
        if n_aug:
            y_aug = np.ones((n_aug, 1)) if y[0, 0] \
                else np.zeros((n_aug, 1))
            return np.concatenate((y, y_aug), axis=0)
        else:
            return y
        
    def configure_dataset(self, analyzer, n_drop=0, n_aug=0):
        """Eine Funktion, die einen Datensatz festlegt.
        :param analyzer:Instanz der ComicAnalyzer-Klasse
        :param n_drop:Anzahl der alten Daten, die von den Trainingsdaten ausgeschlossen werden sollen
        :param n_aug:Anzahl der erweiterten Daten, die zu den Trainingsdaten hinzugefügt werden sollen
        """
        x_ps, x_ng, y_ps, y_ng, t_ps, t_ng = self.get_xs_ys(
            analyzer, analyzer.end_titles, self.thresh_week)
        self.x_test = np.concatenate((x_ps[-50:], x_ng[-50:]), axis=0)
        self.y_test = np.concatenate((y_ps[-50:], y_ng[-50:]), axis=0)
        self.titles_test = t_ps[-50:] + t_ng[-50:]
        self.x_val = np.concatenate((x_ps[-100 : -50], 
                                     x_ng[-100 : -50]), axis=0)
        self.y_val = np.concatenate((y_ps[-100 : -50], 
                                     y_ng[-100 : -50]), axis=0)
        self.x_tra = np.concatenate(
            (self.augment_x(x_ps[n_drop//2 : -100], n_aug//2), 
             self.augment_x(x_ng[n_drop//2 : -100], n_aug//2)), axis=0)
        self.y_tra = np.concatenate(
            (self.augment_y(y_ps[n_drop//2 : -100], n_aug//2), 
             self.augment_y(y_ng[n_drop//2 : -100], n_aug//2)), axis=0)

configure_dataset () erhält zuerst die Eingabe (x_ps, x_ng), das Label (y_ps, y_ng) und den Arbeitsnamen (t_ps, t_ng) mitget_xs_ys () Ich werde. Hier ist die Anzahl der kurzlebigen Arbeitsdaten ("x_ps", "y_ps", "t_ps") gleich der Anzahl der fortlaufenden Arbeitsdaten ("x_ng", "y_ng", "t_ng"). Von diesen werden die letzten 100 Werke als Testdaten verwendet, die verbleibenden letzten 100 Werke werden als Validierungsdaten verwendet und zu viel wird als Trainingsdaten verwendet. Fügen Sie beim Festlegen von Trainingsdaten nach dem Ausschließen alter Daten mit einer Summe von "n_drop" aufgeblasene Daten mit einer Summe von "n_aug" hinzu.

4.2 Build-Berechnungsdiagramm: build_graph ()

ComicNet


    def build_graph(self, r=0.001, n_h=7, stddev=0.01):
        """Eine Funktion, die ein mehrschichtiges Perzeptron erstellt.
        :param r:Lernrate
        :param n_h:Anzahl der Knoten mit versteckten Ebenen
        :param stddev:Standardabweichung der Anfangsverteilung von Variablen
        """
        tf.reset_default_graph()
        
        #Eingabeebene und Ziel
        n_y = self.y_test.shape[1]
        self.x = tf.placeholder(tf.float32, [None, self.n_x + 1], name='x')
        self.y = tf.placeholder(tf.float32, [None, n_y], name='y')
        
        #Versteckte Ebene (1. Ebene)
        self.w_h_1 = tf.Variable(
            tf.truncated_normal((self.n_x + 1, n_h), stddev=stddev))
        self.b_h_1 = tf.Variable(tf.zeros(n_h))
        self.logits = tf.add(tf.matmul(self.x, self.w_h_1), self.b_h_1)
        self.logits = tf.nn.relu(self.logits)
        
        #Versteckte Ebene (zweite Ebene)
        self.w_h_2 = tf.Variable(
            tf.truncated_normal((n_h, n_h), stddev=stddev))
        self.b_h_2 = tf.Variable(tf.zeros(n_h))
        self.logits = tf.add(tf.matmul(self.logits, self.w_h_2), self.b_h_2)
        self.logits = tf.nn.relu(self.logits)
        
        #Ausgabeschicht
        self.w_y = tf.Variable(
            tf.truncated_normal((n_h, n_y), stddev=stddev))
        self.b_y = tf.Variable(tf.zeros(n_y))
        self.logits = tf.add(tf.matmul(self.logits, self.w_y), self.b_y)
        tf.summary.histogram('logits', self.logits)
        
        #Verlustfunktion
        self.loss = tf.reduce_mean(
            tf.nn.sigmoid_cross_entropy_with_logits(
                logits=self.logits, labels=self.y))
        tf.summary.scalar('loss', self.loss)
        
        #Optimierung
        self.optimizer = tf.train.AdamOptimizer(r).minimize(self.loss)
        self.output = tf.nn.sigmoid(self.logits, name='output')
        correct_prediction = tf.equal(self.y, tf.round(self.output))
        self.acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32),
            name='acc')
        tf.summary.histogram('output', self.output)
        tf.summary.scalar('acc', self.acc)
        
        self.merged = tf.summary.merge_all()

In der Eingabeebene definiert "tf.placeholder" den Eingabetensor ("x") und den Lehrerbezeichnungstensor ("y").

In der verborgenen Schicht definiert "tf.Variable" den Gewichtstensor ("w_h_1", "w_h_2") und die Vorspannung ("b_h_1", "b_h_2"). Hier wird tf.truncated_normal als anfängliche Verteilung von Variable angegeben. truncated_normal ist eine Normalverteilung, die Werte außerhalb des 2-Sigma ausschließt und häufig verwendet wird. Tatsächlich ist die Standardabweichung dieser "abgeschnittenen Normalen" einer der wichtigen Hyperparameter, die die Leistung des Modells beeinflussen. Dieses Mal habe ich mir die Ergebnisse des Vorversuchs angesehen und auf "0,01" gesetzt. tf.add, tf.matmul / matmul), tf.nn.relu wird verwendet, um die Tensoren zu einer verborgenen Schicht zu verbinden. Ich werde. Übrigens wird tf.nn.relu in [ tf.nn.sigmoid](https: //www.tensorflow) geändert. Wenn Sie es als .org / api_docs / python / tf / sigmoid # tfnnsigmoid umschreiben, können Sie Sigmoid als Aktivierungsfunktion verwenden. A7% E5% 8C% 96% E9% 96% A2% E6% 95% B0 # .E3.82.B7.E3.82.B0.E3.83.A2.E3.82.A4.E3.83.89.E9 Sie können .96.A2.E6.95.B0) verwenden. Informationen zu den Aktivierungsfunktionen, die in TensorFlow verwendet werden können, finden Sie unter hier.

Die Ausgabeschicht führt grundsätzlich die gleiche Verarbeitung durch wie die untergetauchte Schicht. Besonders aktiv in der Ausgabeschicht, da es eine Aktivierungsfunktion (Sigmoid) innerhalb der Verlustfunktion enthält tf.nn.sigmoid_cross_entropy_with_logits Beachten Sie, dass Sie die Konvertierungsfunktion nicht verwenden müssen. Indem Sie tf.Variable an tf.summary.scalar übergeben, können Sie die Zeitänderung mit TensorBoard überprüfen. Werden.

Verwenden Sie tf.train.AdamOptimizer als Optimierungsalgorithmus. Informationen zu den Optimierungsalgorithmen, die mit TensorFlow verwendet werden können, finden Sie unter hier. Der endgültige Ausgabewert "logits" wird abgerundet (dh anhand eines Schwellenwerts von 0,5 beurteilt), und die korrekte Antwortrate für das Lehrerlabel "y" wird als "acc" berechnet. Schließlich werden alle Protokollinformationen mit allen tf.summary.merge_all zusammengeführt.

4.3 Lernen: train ()

In TensorFlow wird das Lernen in tf.Session durchgeführt. Sie müssen Variable immer mit [tf.global_variables_initializer ()] initialisieren (https://www.tensorflow.org/api_docs/python/tf/global_variables_initializer) (sonst werden Sie wütend) ..

Trainiere das Modell mit sess.run (self.optimizer). Mehrere erste Argumente von sess.run können durch Tapple angegeben werden. Zum Zeitpunkt von "sess.run ()" ist es außerdem erforderlich, "Platzhalter" im Wörterbuchformat einen Wert zuzuweisen. Ersetzen Sie während des Trainings "x_tra" und "x_tra" und während der Validierung "x_val" und "y_val".

Sie können die Protokollinformationen für TensorBoard mit tf.summary.FileWriter speichern. Sie können das trainierte Modell auch mit tf.train.Saver speichern.

ComicNet


    def train(self, epoch=2000, print_loss=False, save_log=False, 
              log_dir='./logs/1', log_name='', save_model=False,
              model_name='prediction_model'):
        """Eine Funktion, die ein mehrschichtiges Perzeptron trainiert und Protokolle und trainierte Modelle speichert.
        :param epoch:Anzahl der Epochen
        :pram print_loss:Gibt an, ob der Verlauf der Verlustfunktion ausgegeben werden soll
        :param save_log:Gibt an, ob das Protokoll gespeichert werden soll
        :param log_dir:Protokollspeicherverzeichnis
        :param log_name:Protokoll Speichername
        :param save_model:Gibt an, ob das trainierte Modell gespeichert werden soll
        :param model_name:Gespeicherter Name des trainierten Modells
        """
        with tf.Session() as sess:
            sess.run(tf.global_variables_initializer()) #Variable Initialisierung
            
            #Einstellungen zum Speichern von Protokollen
            log_tra = log_dir + '/tra/' + log_name 
            writer_tra = tf.summary.FileWriter(log_tra)
            log_val = log_dir + '/val/' + log_name
            writer_val = tf.summary.FileWriter(log_val)        

            for e in range(epoch):
                feed_dict = {self.x: self.x_tra, self.y: self.y_tra}
                _, loss_tra, acc_tra, mer_tra = sess.run(
                        (self.optimizer, self.loss, self.acc, self.merged), 
                        feed_dict=feed_dict)
                
                # validation
                feed_dict = {self.x: self.x_val, self.y: self.y_val}
                loss_val, acc_val, mer_val = sess.run(
                    (self.loss, self.acc, self.merged),
                    feed_dict=feed_dict)
                
                #Protokoll speichern
                if save_log:
                    writer_tra.add_summary(mer_tra, e)
                    writer_val.add_summary(mer_val, e)
                
                #Verlustfunktionsausgabe
                if print_loss and e % 500 == 0:
                    print('# epoch {}: loss_tra = {}, loss_val = {}'.
                          format(e, str(loss_tra), str(loss_val)))
            
            #Modell speichern
            if save_model:
                saver = tf.train.Saver()
                _ = saver.save(sess, './models/' + model_name)

4.4 Test: test ()

ComicNet


    def test(self, model_name='prediction_model'):
        """Eine Funktion, die das angegebene Modell liest und testet.
        :param model_name:Der Name des zu ladenden Modells
        """
        tf.reset_default_graph()
        loaded_graph = tf.Graph()
        
        with tf.Session(graph=loaded_graph) as sess:
            
            #Modell laden
            loader = tf.train.import_meta_graph(
                './models/{}.meta'.format(model_name))
            loader.restore(sess, './models/' + model_name)
            
            x_loaded = loaded_graph.get_tensor_by_name('x:0')
            y_loaded = loaded_graph.get_tensor_by_name('y:0')
            
            loss_loaded = loaded_graph.get_tensor_by_name('loss:0')
            acc_loaded = loaded_graph.get_tensor_by_name('acc:0')
            output_loaded = loaded_graph.get_tensor_by_name('output:0')
        
            # test
            feed_dict = {x_loaded: self.x_test, y_loaded: self.y_test}
            loss_test, acc_test, output_test = sess.run(
                (loss_loaded, acc_loaded, output_loaded), feed_dict=feed_dict)
            return acc_test, output_test

test () ist eine Mitgliedsfunktion, die ein trainiertes mehrschichtiges Perzeptron testet. Verwenden Sie tf.train.import_meta_graph, um das trainierte Modell zu laden. Geben Sie Testdaten (x_test, y_test) an feed_dict und führen Siesess.run aus.

5. Experimentieren

5.1 Hyperparametereinstellung

Durch Visualisierung der Genauigkeit (korrekte Antwortrate) und des Verlusts (Ausgabe der Verlustfunktion) von Validierungsdaten mit TensorBoard, Hyperparametern (Lernrate $ r $) (Anzahl der Epochen) ist abgestimmt. Weitere Informationen zu TensorBoard finden Sie unter Offiziell. In diesem Artikel wird der Einfachheit halber nur eine gültige Nummer angepasst. Obwohl Details weggelassen wurden, wurden vorläufige Experimente zur Anzahl der verborgenen Schichten (2), zur Aktivierungsfunktion der verborgenen Schichten (ReLU), zur Standardabweichung der anfänglichen Verteilung der Variablen (0,01) und zum Optimierungsalgorithmus (Adam) durchgeführt. Es wurde leicht mit eingestellt.

rs = [n * 10 ** m for m in range(-4, -1) for n in range(1, 10)]
datasets = [
    {'n_drop':0, 'n_aug':0},
    {'n_drop':173, 'n_aug':0},
    {'n_drop':173, 'n_aug':173},
]

wjnet = ComicNet()

for i, dataset in enumerate(datasets):
    wjnet.configure_dataset(wj, n_drop=dataset['n_drop'], 
                            n_aug=dataset['n_aug'])
    log_dir = './logs/dataset={}/'.format(i + 1)
    for r in rs:
        log_name = str(r)
        wjnet.build_graph(r=r)
        wjnet.train(epoch=20000, save_log=True, log_dir=log_dir, 
                log_name=log_name)
        print('Saved log of dataset={}, r={}'.format(i + 1, r))

Betrachten wir für Datensatz 1 die Genauigkeit und den Verlust von Validierungsdaten mit TensorBoard.

tensorboard --logdir=./logs/dataset=1/val

tensorboard.png

Die horizontale Achse ist die Anzahl der Epochen. Suchen Sie von hier aus nach $ r $ und $ epoch $, die den Validierungsverlust minimieren.

dataset1.png

Für Datensatz 1 scheinen $ r = 0,0003 $ und $ epoch = 2000 $ gut zu sein. Machen Sie dasselbe für Datensatz 2 und Datensatz 3.

dataset2.png

Für Datensatz 2 scheinen $ r = 0,0005 $ und $ epoch = 2000 $ gut zu sein.

dataset3.png

Für Datensatz 3 scheinen $ r = 0,0001 $ und $ epoch = 8000 $ gut zu sein.

5.2 Lernen

Trainieren Sie für jeden Datensatz mit den oben angepassten Hyperparametern und speichern Sie das Modell.

params = [
    {'n_drop':0, 'n_aug':0, 'r':0.0003, 
     'e': 2000, 'name':'1: Original'},
    {'n_drop':173, 'n_aug':0, 'r':0.0005, 
     'e': 2000, 'name':'2: Filtered'},
    {'n_drop':173, 'n_aug':173, 'r':0.0001, 
     'e': 8000, 'name':'3: Filtered+Augmented'}
]

wjnet = ComicNet()
for i, param in enumerate(params):
    model_name = str(i + 1)
    wjnet.configure_dataset(wj, n_drop=param['n_drop'],
                            n_aug=param['n_aug'])
    wjnet.build_graph(r=param['r'])
    wjnet.train(save_model=True, model_name=model_name, epoch=param['e'])
    print('Trained', param['name'])

5.3 Bewertung

Bewerten Sie die Leistung mit "ComicNet.test ()".

accs = []
outputs = []
for i, param in enumerate(params):
    model_name = str(i + 1)
    acc, output = wjnet.test(model_name)
    accs.append(acc)
    outputs.append(output)
    print('Test model={}: acc={}'.format(param['name'], acc))

plt.bar(range(3), accs, tick_label=[param['name'] for param in params])
for i, acc in enumerate(accs):
    plt.text(i - 0.1, acc-0.3, str(acc), color='w')
plt.ylabel('Accuracy') 
result.png

Selbst wenn es zufällig klassifiziert wird, sollte es $ acc = 0,5 $ sein, also war es ein subtiles Ergebnis ... Glücklicherweise konnte ich die Auswirkungen von Filter und Augmentation bestätigen.

5.4 Überlegung

Lassen Sie uns etwas tiefer in die Ergebnisse des leistungsstärksten Modells 3 (gefiltert + erweitert) eintauchen.

idx_sorted = np.argsort(output.reshape((-1)))
output_sorted = np.sort(output.reshape((-1)))

y_sorted = np.array([wjnet.y_test[i, 0] for i in idx_sorted])
title_sorted = np.array([wjnet.titles_test[i] for i in idx_sorted])

t_ng = np.logical_and(y_sorted == 0, output_sorted < 0.5)
f_ng = np.logical_and(y_sorted == 1, output_sorted < 0.5)
t_ps = np.logical_and(y_sorted == 1, output_sorted >= 0.5)
f_ps = np.logical_and(y_sorted == 0, output_sorted >= 0.5)

weeks = np.array([len(wj.extract_item(title)) for title in title_sorted])
plt.plot(weeks[t_ng], output_sorted[t_ng], 'o', ms=10,
        alpha=0.5, c='b', label='True negative')
plt.plot(weeks[f_ng], output_sorted[f_ng], 'o', ms=10,
        alpha=0.5, c='r', label='False negative')
plt.plot(weeks[t_ps], output_sorted[t_ps], '*', ms=15,
        alpha=0.5, c='b', label='True positive')
plt.plot(weeks[f_ps], output_sorted[f_ps], '*', ms=15,
         alpha=0.5, c='r', label='False positive')
plt.ylabel('Output')
plt.xlabel('Serialized weeks')
plt.xscale('log')
plt.ylim(0, 1)
plt.legend()
scatter.png

Die obige Abbildung zeigt die Beziehung zwischen der tatsächlichen Serialisierungsperiode und der Ausgabe des Klassifikators. Blau ist eine korrekt klassifizierte Arbeit (True) und Rot ist eine falsch klassifizierte Arbeit (False). Sterne sind Werke, die als kurzlebige Werke klassifiziert sind (positiv), und Kreise sind Werke, die als kontinuierliche Werke klassifiziert sind (negativ). Es wird davon ausgegangen, dass die Klassifizierungsleistung besser ist, da es viele blaue Werke gibt und die Verteilung in der Grafik von links oben nach rechts unten konzentriert ist.

Zunächst bin ich besorgt, dass es keine Ausgabe von 0,75 oder mehr gibt. Läuft das Lernen nicht gut? Es ist nicht gut verstanden ... Das nächste, worüber Sie sich Sorgen machen müssen, ist das falsche Positiv oben rechts in der Grafik. Einige populäre Werke, die seit mehr als 100 Wochen serialisiert wurden, wurden als kurzlebige Werke falsch klassifiziert. Vergleichen wir daher die Veröffentlichungsreihenfolge (schlechteste) der repräsentativen Werke jedes Klassifizierungsergebnisses.

plt.figure(figsize=(12, 8))

plt.subplot(2, 2, 1)
for output, week, title in zip(
    output_sorted[t_ps][-5:], weeks[t_ps][-5:], title_sorted[t_ps][-5:]):
    plt.plot(range(1, 8), wj.extract_item(title)[:7], 
             label='{0} ({1:>3}, {2:.2f})'.format(title[:5], week, output))
plt.ylabel('Worst')
plt.ylim(0, 23)
plt.title('Teil von True Positive (korrekt klassifizierte kurzlebige Arbeit)')
plt.legend()

plt.subplot(2, 2, 2)
for output, week, title in zip(
    output_sorted[f_ps], weeks[f_ps], title_sorted[f_ps]):
    if week > 100:
        plt.plot(range(1, 8), wj.extract_item(title)[:7], 
                 label='{0} ({1:>3}, {2:.2f})'.format(title[:5], week, output))
plt.ylim(0, 23)
plt.title('Teil von False Positive (eine als kurzlebige Arbeit falsch eingestufte Fortsetzung)')
plt.legend()
    
plt.subplot(2, 2, 3)
for output, week, title in zip(
    output_sorted[f_ng][:5], weeks[f_ng][:5], title_sorted[f_ng][:5]):
    plt.plot(range(1, 8), wj.extract_item(title)[:7], 
             label='{0} ({1:>3}, {2:.2f})'.format(title[:5], week, output))
plt.xlabel('Weeks')
plt.ylabel('Worst')
plt.ylim(0, 23)
plt.title('Teil von False Negative (kurzlebige Arbeit, die als Fortsetzung falsch eingestuft wurde)')
plt.legend()
    
plt.subplot(2, 2, 4)
for output, week, title in zip(
    output_sorted[t_ng][:5], weeks[t_ng][:5], title_sorted[t_ng][:5]):
    plt.plot(range(1, 8), wj.extract_item(title)[:7], 
             label='{0} ({1:>3}, {2:.2f})'.format(title[:5], week, output))
plt.xlabel('Weeks')
plt.ylim(0, 23)
plt.title('Teil von True Negative (korrekt klassifizierte Fortsetzung)')
plt.legend()
worsts.png

Die horizontale Achse ist die Veröffentlichungswoche, und die vertikale Achse ist die Veröffentlichungsreihenfolge, die ab dem Ende des Buches gezählt wird. Die Legende zeigt den Titel der Arbeit (Serialisierungszeitraum, Ausgabewert). Es ist zu erkennen, dass die Werke mit False Positive (oben rechts) in der Reihenfolge der Veröffentlichung bis zur 7. Woche einen stärkeren Abwärtstrend aufweisen als die Werke mit True Negative (unten rechts). Umgekehrt kann das falsch positive Werk (oben rechts) als ein populäres Werk angesehen werden, das die Minderwertigkeit in den frühen Stadien zurückgespult hat. Auch die Reihenfolge der Veröffentlichung von Falsch-Negativ-Werken (unten links) bis zu 7 Wochen weist einen leichten Abwärtstrend auf, und zumindest in meinen Augen ist sie nicht von dem von Wahr-Negativ-Werken (unten rechts) zu unterscheiden. Ich kann den Grund für die Fehlklassifizierung verstehen.

Nachfolgend sind als Referenz die Ausgabewerte aller 100 Werke aufgetragen.

labels = np.array(['{0} ({1:>3})'.format(title[:6], week)
                   for title, week in zip(title_sorted, weeks) ])

plt.figure(figsize=(4, 18))
plt.barh(np.arange(100)[t_ps], output_sorted[t_ps], color='b')
plt.barh(np.arange(100)[f_ps], output_sorted[f_ps], color='r')
plt.barh(np.arange(100)[f_ng], output_sorted[f_ng], color='r')
plt.barh(np.arange(100)[t_ng], output_sorted[t_ng], color='b')
plt.yticks(np.arange(100), labels)
plt.xlim(0, 1)
plt.xlabel('Output')
for i, out in enumerate(output_sorted):
    plt.text(out + .01, i - .5, '{0:.2f}'.format(out))
output.png

Die horizontale Achse repräsentiert den Ausgabewert. Die Klammern neben dem Titel der Arbeit geben den Serialisierungszeitraum an. Blau zeigt das korrekte Klassifizierungsergebnis an und Rot zeigt das falsche Klassifizierungsergebnis an. Je näher der Ausgabewert an 1 liegt, desto mehr wird davon ausgegangen, dass es sich um eine kurzlebige Arbeit handelt.

6. Fazit

Tatsächlich ist dieser Artikel das Ergebnis dessen, was ich in Deep Learning Foundation Nanodegree [^ nd101] gelernt habe. Ich fing an zu schreiben. Deshalb habe ich mich hartnäckig an das mehrschichtige Perceptron gehalten. Schließlich ist es sehr schwierig, maschinelles Lernen auf reale Probleme anzuwenden. Ohne dieses Thema wäre ich absolut frustriert gewesen.

Die endgültige Leistung war enttäuschend, aber es war gut, die Auswirkungen der Filterung und Erweiterung des Datensatzes zu sehen. Ich denke, dass sich die Leistung etwas verbessern wird, wenn Sie die Hyperparameter (n_drop, n_aug) anpassen, die diesmal festgelegt wurden. Alternativ können Sie, wie Sie in Warum neuronale Netze SVM nicht schlagen können darauf hingewiesen haben, andere Methoden des maschinellen Lernens wie SVM anwenden. Es kann sein. Ich bin erschöpft und werde es nicht tun.

Seit der Veröffentlichung des ersten Teils haben wir Feedback von vielen Menschen erhalten, sowohl real als auch online. Es dreht sich alles um Sonntagsprogrammierer. Ich hoffe, dass ich in Zukunft mit Ihnen zusammenarbeiten kann. Vielen Dank für das Lesen bis zum Ende!

[^ nd101]: Ich bin ein sogenannter März-Student. Vielen Dank.

Verweise

Bei der Erstellung dieses Artikels habe ich auf Folgendes verwiesen. Vielen Dank! : Bogen:

  1. Japanisch mit Matplotlib unter Ubuntu zeichnen: Informationen zur japanischen Ausgabe
  2. Anwenden von tiefem Lernen auf reale Probleme - Merantix: Wenn das Datenetikett voreingenommen ist Informationen zur Bewältigungsmethode
  3. Hinweise zur Fehlerrückvermehrungsmethode: Über mehrschichtiges Perzeptron im Allgemeinen
  4. Warum müssen wir Validierungs- und Testsätze für überwachtes Lernen trennen? : Umgang mit verschiedenen Datensätzen
  5. Ian Goodfellow und Yoshua Bengio und Aaron Courville, Deep Learning, MIT Press, 2016: Datensatzerweiterung im Allgemeinen (Abschnitt 7.4)
  6. So erhöhen Sie die Anzahl der Datensatzbilder für maschinelles Lernen: Informationen zur Datensatzerweiterung für Bilddaten
  7. Ich bin weder Programmierer noch Datenwissenschaftler, aber ich habe Tensorflow einen Monat lang berührt, daher ist es sehr einfach zu verstehen: Über TensorFlow
  8. TensorBoard: Informationen zur Anpassung von Hyperparametern mit TensorBoard
  9. Warum neuronales Netz SVM nicht schlagen kann: Zukünftige Forschungspolitik

Recommended Posts

Vorhersage kurzlebiger Arbeiten von Weekly Shonen Jump durch maschinelles Lernen (Teil 2: Lernen und Bewertung)
Vorhersage kurzlebiger Arbeiten von Weekly Shonen Jump durch maschinelles Lernen (Teil 1: Datenanalyse)
Klassifizierung von Gitarrenbildern durch maschinelles Lernen Teil 1
Klassifizierung von Gitarrenbildern durch maschinelles Lernen Teil 2
Vorhersage des Vorhandenseins oder Nichtvorhandenseins von Untreue durch maschinelles Lernen
Bedeutung des maschinellen Lernens und des Mini-Batch-Lernens
[Maschinelles Lernen] Zusammenfassung und Ausführung der Modellbewertung / Indikatoren (mit Titanic-Datensatz)
Vorhersage des Strombedarfs durch maschinelles Lernen Teil 2
Bewertungsmethode des Regressionsproblems des maschinellen Lernens (mittlerer quadratischer Fehler und Entscheidungskoeffizient)
Maschinelles Lernen: Bilderkennung von MNIST mithilfe von PCA und Gaussian Native Bayes
Ich habe versucht, das Vorhandensein oder Nichtvorhandensein von Schnee durch maschinelles Lernen vorherzusagen.
Maschinelles Lernen eines jungen Ingenieurs Teil 1
Numerai Turnier-Fusion von traditionellen Quants und maschinellem Lernen-
Paralleles Lernen von Deep Learning durch Keras und Kubernetes
Zusammenfassung der beim maschinellen Lernen verwendeten Bewertungsfunktionen
Analyse der gemeinsamen Raumnutzung durch maschinelles Lernen
[Übersetzung] scikit-learn 0.18 Einführung in maschinelles Lernen durch Tutorial scikit-learn
Maschinelles Lernen eines jungen Ingenieurs Teil 2
Angemessene Preisschätzung von Mercari durch maschinelles Lernen
Python-Lernnotiz für maschinelles Lernen von Chainer Kapitel 1 und 2
Prognostizieren Sie das Geschlecht von Twitter-Nutzern durch maschinelles Lernen
Eine konkrete Methode zur Vorhersage von Pferderennen und zur Simulation der Wiederherstellungsrate durch maschinelles Lernen
Ich habe versucht, die Yin- und Yang-Klassifikation hololiver Mitglieder durch maschinelles Lernen zu überprüfen
Leistungsüberprüfung der Datenvorverarbeitung für maschinelles Lernen (numerische Daten) (Teil 2)
Maschinelles Lernen mit Nogisaka 46 und Keyakizaka 46 Teil 1 Einführung
Leistungsüberprüfung der Datenvorverarbeitung für maschinelles Lernen (numerische Daten) (Teil 1)
Verwendung der offenen Daten von Data City Sabae zur Vorhersage des Werts des Wasserstandsmessers durch maschinelles Lernen Teil 2