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.
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.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.
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()
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()
In diesem Artikel werden wir das Problem der Klassifizierung, ob es sich um eine kurzlebige Arbeit handelt oder nicht, anhand der folgenden Eingaben herausfordern.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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)
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.
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
Die horizontale Achse ist die Anzahl der Epochen. Suchen Sie von hier aus nach $ r $ und $ epoch $, die den Validierungsverlust minimieren.
Für Datensatz 1 scheinen $ r = 0,0003 $ und $ epoch = 2000 $ gut zu sein. Machen Sie dasselbe für Datensatz 2 und Datensatz 3.
Für Datensatz 2 scheinen $ r = 0,0005 $ und $ epoch = 2000 $ gut zu sein.
Für Datensatz 3 scheinen $ r = 0,0001 $ und $ epoch = 8000 $ gut zu sein.
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'])
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')
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.
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()
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()
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))
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.
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.
Bei der Erstellung dieses Artikels habe ich auf Folgendes verwiesen. Vielen Dank! : Bogen:
Recommended Posts