Cet article est une suite de Prédire les travaux de courte durée de Weekly Shonen Jump par Machine Learning (Partie 1: Analyse des données). En utilisant les données acquises dans la première partie, nous allons implémenter et évaluer un classifieur avec un perceptron multicouche. Par la suite, le saut fait référence au saut hebdomadaire des garçons.
La figure ci-dessus fait partie du résultat de l'évaluation. Lorsque vous utilisez le meilleur modèle (filtré + augmenté), ** Si vous entrez l'ordre de publication [^ ordre de publication] jusqu'à la 7e semaine et le nombre de couleurs, il y a 65% de chances que le travail soit terminé dans les 20 semaines. Il s'est avéré prévisible ** [^ jump]. Les 100 dernières œuvres enregistrées dans la base de données des arts médiatiques de l'Agence culturelle ont été utilisées pour l'évaluation, et d'autres œuvres ont été utilisées pour l'apprentissage et l'ajustement des paramètres. .. J'ai inventé diverses choses, mais cette performance était la limite avec ma propre puissance. Les détails sont expliqués ci-dessous. jupyter notebook est ici, le code source est ici -comic-end).
Cet article n'exprime pas d'opinion sur la politique éditoriale de Jump et ne fait pas appel à la fin ou à la poursuite inappropriée de tout travail. Bonne chance! Bonne chance artiste manga!
[^ Ordre d'affichage]: La rédaction de Jump semble avoir nié le principe de la suprématie des questionnaires, en disant: "Nous ne considérons pas nécessairement uniquement les résultats des questionnaires des lecteurs." Le département éditorial de "Jump" nie les rumeurs sur le principe suprême du questionnaire ... Les lecteurs sont compliqués
[^ Jump]: Comme mentionné ci-dessus, en réalité, le service éditorial de saut décide du travail interrompu en tenant compte de divers facteurs. J'espère que vous comprenez cet article comme une illusion d'un fan de saut.
2.1 anaconda
Créez l'environnement virtuel suivant comic
avec ʻ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
Le fichier yml
est ici. tensorflow
et scikit-learn
sont inclus. De plus, depuis que j'ai utilisé pairplot ()
dans la première partie, seaborn
/) Est inséré.
On suppose que le wj-api.json
obtenu dans Partie 1 se trouve dans le répertoire data
. Supposons également que le ComicAnalyzer
introduit dans la Partie 1 est défini dans comic.py
.
import comic
wj = comic.ComicAnalyzer()
Je veux afficher le titre du dessin animé en japonais, alors réglez-le en faisant référence à Dessiner le japonais avec matplotlib sur Ubuntu. Si vous n'utilisez pas Ubuntu, veuillez prendre les mesures appropriées.
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()
Dans cet article, nous allons remettre en question le problème de la classification selon qu'il s'agit d'un travail de courte durée ou non en fonction de l'entrée suivante.
En entrée, nous utiliserons un total de 8 dimensions d'informations sur l'ordre de publication de chaque semaine jusqu'à la 7ème semaine de sérialisation et le nombre total de couleurs. La raison d'utiliser les données jusqu'à la 7e semaine est que je voulais prédire la fin de la sérialisation la plus courte (8 semaines) ces dernières années, au plus tard une semaine avant. La raison d'utiliser le nombre de couleurs ainsi que l'ordre de publication est d'améliorer la précision de la prédiction. Intuitivement, les œuvres les plus populaires ont tendance à avoir plus de couleurs.
Dans Partie 1, les «œuvres de courte durée» sont définies comme suit.
Dans cet article, nous utiliserons l'apprentissage automatique pour prédire les travaux de courte durée (travaux qui seront achevés dans un délai de 10 semaines).
Comme expérience préliminaire, j'ai essayé de classer les œuvres de courte durée avec cette définition, mais je n'ai pas bien appris. En analysant à nouveau wj-api.json
, nous pouvons voir que très peu de travaux sont terminés en 10 semaines.
La figure de gauche est la distribution cumulative de toutes les œuvres, et la figure de droite se concentre sur jusqu'à 50 semaines dans la figure de gauche. L'axe horizontal correspond à la période de publication et l'axe vertical au ratio. Sur la figure de droite, on peut voir que moins de 10% des travaux ont été achevés en 10 semaines. Pourquoi Neural Net ne peut pas battre SVM Comme vous l'avez souligné, Multilayer Perceptron n'est pas bon pour apprendre des données déséquilibrées [^ Sticking] ..
Selon Applying deep learning to real-world problems --Merantix, lorsque l'étiquette de données est biaisée Un changement d'étiquetage a été proposé comme l'une des contre-mesures. Par conséquent, dans cet article, pour des raisons de commodité, la définition des travaux de courte durée sera modifiée en ** travaux achevés dans un délai de 20 semaines ** (pour la prédiction des travaux achevés dans un délai de 10 semaines, veuillez le laisser comme un futur devoir ...). Si le seuil est fixé à 20 semaines, environ la moitié des œuvres peuvent être traitées comme des œuvres de courte durée.
[^ Engagement]: Il est alors raisonnable de souligner que vous devez utiliser SVM. Cette fois, j'ai été particulier à propos de Perceptron pour mes études.
Voici un modèle de Perceptron multicouche utilisé dans cet article. Pour plus d'informations sur le perceptron multicouche, voir Notes sur la méthode de rétropropagation.
La couche cachée comprend 7 nœuds et 2 couches. En tant que fonction d'activation de couche masquée, [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) .. La couche de sortie affiche la probabilité qu'il s'agisse d'un travail de courte durée et en tant que fonction d'activation [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. Utilisez 95.B0). Utilisez Adam pour apprendre. Le taux d'apprentissage $ r $ est ajusté par TensorBoard. À propos, pour le modèle ci-dessus (nombre de couches cachées, nombre de couches cachées, fonction d'activation des couches cachées, algorithme d'optimisation), celui avec les meilleures performances dans l'expérience préliminaire est sélectionné.
Dans cet article, nous utiliserons 273 œuvres éphémères et 273 autres œuvres (ci-après dénommées œuvres continues), pour un total de 546 œuvres. À partir de nouveaux travaux, 100 œuvres sont utilisées comme données de test, 100 œuvres sont utilisées comme données de validation et 346 œuvres sont utilisées comme données de formation. Les données de test sont les données pour l'évaluation finale, les données de validation sont les données pour le réglage d'hyper paramètre et les données d'entraînement sont les données pour la formation. Pour ces derniers, Pourquoi avons-nous besoin de séparer l'ensemble de validation et l'ensemble de test pour l'apprentissage supervisé? est détaillé.
Dans cet article, nous utiliserons les données d'entraînement des trois manières suivantes. «x_test» et «y_test» représentent les données de test, «x_val» et «y_val» représentent les données de validation, et «x_tra» et «y_tra» représentent les données de formation.
Dans l'ensemble de données 1, les 346 œuvres de données d'entraînement sont utilisées pour l'apprentissage. L'ensemble de données 2 exclut environ la moitié des anciennes œuvres des données de formation et les utilise pour l'apprentissage. C'est parce que je pensais que certaines des œuvres de données de formation étaient trop anciennes pour être adaptées à l'apprentissage de la politique de coupure actuelle du service éditorial de saut (devenir du bruit). Dans l'ensemble de données 3, l'ensemble de données 2 est gonflé par l'augmentation de l'ensemble de données et utilisé pour l'apprentissage. En effet, nous pensions que le jeu de données 2 avait trop peu de données d'entraînement pour fournir des performances de généralisation suffisantes.
L'augmentation de l'ensemble de données est une technique de traitement des données pour gonfler les données d'entraînement. Il est connu pour être efficace principalement pour améliorer les performances de la reconnaissance d'image et de la reconnaissance vocale. Pour plus de détails, voir Section 7.4 du livre Deep Learning et Comment augmenter le nombre d'images dans un ensemble de données d'apprentissage automatique. Veuillez vous référer à / bohemian916 / items / 9630661cd5292240f8c7). Le thème derrière cet article est d'évaluer l'efficacité de l'augmentation de Dataset pour prédire l'arrêt des magazines hebdomadaires de bandes dessinées. Dans cet article, l'augmentation des données est effectuée par la méthode indiquée ci-dessous.
En gros, de nouvelles données sont générées en sélectionnant au hasard deux données avec le même libellé et en prenant leurs moyennes pondérées au hasard. Derrière cela, il y a une hypothèse selon laquelle les travaux avec des notes intermédiaires (par ordre de publication) de plusieurs œuvres de courte durée sont également des œuvres de courte durée. Intuitivement, cela semble être une hypothèse pas si mauvaise.
La classe ComicNet ()
pour la gestion du perceptron multicouche est définie ci-dessous. ComicNet ()
définit diverses données (test, validation et train), construit un perceptron multicouche, entraîne et teste. TensorFlow est utilisé pour l'implémentation. Concernant TensorFlow, Je ne suis ni programmeur ni data scientist, mais j'ai touché Tensorflow pendant un mois, donc c'est super facile à comprendre / items / c977c79b76c5979874e8) est détaillé.
ComicNet()
class ComicNet():
"""Cette classe gère un perceptron multicouche qui identifie si un travail de manga est de courte durée ou non.
:param thresh_semaine: Seuil qui sépare les œuvres éphémères des autres.
:param n_x: Nombre d'ordres de publication à saisir dans le perceptron multicouche.
"""
def __init__(self, thresh_week=20, n_x=7):
self.n_x = n_x
self.thresh_week = thresh_week
Voici une brève description de chaque fonction membre.
configure_dataset ()
etc.ComicNet
def get_x(self, analyzer, title):
"""C'est une fonction pour obtenir l'ordre de publication normalisé du travail spécifié jusqu'à la semaine spécifiée."""
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):
"""Une fonction pour savoir si le travail spécifié est un travail de courte durée."""
return int(len(analyzer.extract_item(title)) <= thresh_week)
def get_xs_ys(self, analyzer, titles, thresh_week):
"""Une fonction qui renvoie les fonctionnalités, l'étiquette et le titre du groupe de travail spécifié.
y==0 et y==Le nombre de données de 1 est aligné et renvoyé.
"""
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 et ys==Alignez le nombre de données sur 1.
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):
"""Une fonction qui génère artificiellement un nombre spécifié de x données."""
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):
"""Une fonction qui génère artificiellement un nombre spécifié de données y."""
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):
"""Une fonction qui définit un ensemble de données.
:param analyzer:Instance de la classe ComicAnalyzer
:param n_drop:Nombre d'anciennes données à exclure des données d'entraînement
:param n_aug:Nombre de données augmentées à ajouter aux données d'entraînement
"""
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 ()
obtient d'abord l'entrée (x_ps
, x_ng
), l'étiquette (y_ps
, y_ng
) et le nom du travail (t_ps
, t_ng
) avecget_xs_ys ()
. Je vais. Ici, le nombre de données de travail de courte durée (x_ps
, y_ps
, t_ps
) est égal au nombre de données de travail continu ( x_ng
, y_ng
, t_ng
). Parmi ceux-ci, les 100 dernières œuvres sont utilisées comme données de test, les 100 dernières œuvres restantes sont utilisées comme données de validation, et trop sont utilisées comme données de formation. Lors de la définition des données d'entraînement, après avoir exclu les anciennes données avec un total de n_drop
, ajoutez des données gonflées avec un total de n_aug
.
build_graph ()
ComicNet
def build_graph(self, r=0.001, n_h=7, stddev=0.01):
"""Une fonction qui construit un perceptron multicouche.
:param r:Taux d'apprentissage
:param n_h:Nombre de nœuds de couche masqués
:param stddev:Écart type de la distribution initiale des variables
"""
tf.reset_default_graph()
#Couche d'entrée et cible
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')
#Couche cachée (1ère couche)
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)
#Couche cachée (deuxième couche)
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)
#Couche de sortie
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)
#Fonction de perte
self.loss = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
logits=self.logits, labels=self.y))
tf.summary.scalar('loss', self.loss)
#optimisation
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()
Dans la couche d'entrée, tf.placeholder
définit le tenseur d'entrée ( x
) et le tenseur d'étiquette d'enseignant (y
).
Dans la couche cachée, tf.Variable
définit le tenseur de poids ( w_h_1
, w_h_2
) et le biais ( b_h_1
, b_h_2
). Ici, tf.truncated_normal
est donné comme distribution initiale de Variable
. truncated_normal
est une distribution normale qui exclut les valeurs en dehors des 2 sigma et est souvent utilisée. En fait, l'écart type de cette truncated_normal
est l'un des hyper paramètres importants qui affectent les performances du modèle. Cette fois, j'ai regardé les résultats de l'expérience préliminaire et l'ai réglé à "0,01". tf.add
, tf.matmul
/ matmul), tf.nn.relu
est utilisé pour connecter les tenseurs pour former une couche cachée. Je vais. À propos, tf.nn.relu
est remplacé par [ tf.nn.sigmoid
](https: //www.tensorflow) Si vous le réécrivez en .org / api_docs / python / tf / sigmoid # tfnnsigmoid), vous pouvez utiliser Sigmoid comme fonction d'activation. 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 Vous pouvez utiliser .96.A2.E6.95.B0). Veuillez vous reporter à ici pour les fonctions d'activation qui peuvent être utilisées dans TensorFlow.
La couche de sortie effectue essentiellement le même traitement que la couche submergée. Particulièrement actif dans la couche de sortie car il contient une fonction d'activation (sigmoid) à l'intérieur de la fonction de perte tf.nn.sigmoid_cross_entropy_with_logits
Notez que vous n'êtes pas obligé d'utiliser la fonction de conversion. En passant tf.Variable
à tf.summary.scalar
, vous pouvez vérifier le changement d'heure avec TensorBoard. Devenir.
Utilisez tf.train.AdamOptimizer
comme algorithme d'optimisation. Veuillez consulter ici pour les algorithmes d'optimisation qui peuvent être utilisés avec TensorFlow. La valeur de sortie finale «logits» est arrondie (c'est-à-dire jugée par un seuil de 0,5), et le taux de réponse correct pour l'étiquette d'enseignant «y» est calculé comme «acc». Enfin, toutes les informations du journal sont fusionnées avec tous les tf.summary.merge_all
.
train ()
Dans TensorFlow, l'apprentissage est effectué dans tf.Session
. Vous devez toujours initialiser Variable
avec tf.global_variables_initializer ()
(sinon vous vous fâcherez) ..
Entraînez le modèle avec sess.run (self.optimizer)
. Plusieurs premiers arguments de sess.run
peuvent être spécifiés par tapple. Aussi, au moment de sess.run ()
, il est nécessaire d'attribuer une valeur à placeholder
au format dictionnaire. Remplacez «x_tra» et «x_tra» pendant la formation, et remplacez «x_val» et «y_val» pendant la validation.
Vous pouvez enregistrer les informations du journal pour TensorBoard avec tf.summary.FileWriter
. Vous pouvez également enregistrer le modèle entraîné avec tf.train.Saver
.
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'):
"""Une fonction qui entraîne un perceptron multicouche et enregistre les journaux et les modèles entraînés.
:param epoch:Nombre d'époques
:pram print_loss:Indique s'il faut sortir l'historique de la fonction de perte
:param save_log:S'il faut enregistrer le journal
:param log_dir:Répertoire de stockage des journaux
:param log_name:Nom de sauvegarde du journal
:param save_model:S'il faut enregistrer le modèle entraîné
:param model_name:Nom enregistré du modèle entraîné
"""
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) #Initialisation variable
#Paramètres d'enregistrement des journaux
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)
#Enregistrer le journal
if save_log:
writer_tra.add_summary(mer_tra, e)
writer_val.add_summary(mer_val, e)
#Sortie de fonction de perte
if print_loss and e % 500 == 0:
print('# epoch {}: loss_tra = {}, loss_val = {}'.
format(e, str(loss_tra), str(loss_val)))
#Enregistrer le modèle
if save_model:
saver = tf.train.Saver()
_ = saver.save(sess, './models/' + model_name)
test ()
ComicNet
def test(self, model_name='prediction_model'):
"""Une fonction qui lit et teste le modèle spécifié.
:param model_name:Le nom du modèle à charger
"""
tf.reset_default_graph()
loaded_graph = tf.Graph()
with tf.Session(graph=loaded_graph) as sess:
#Modèle de charge
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 ()
est une fonction membre qui teste un perceptron multicouche entraîné. Utilisez tf.train.import_meta_graph
pour charger le modèle entraîné. Donnez les données de test (x_test
, y_test
) à feed_dict
et exécutezsess.run
.
En visualisant la précision (taux de réponse correct) et la perte (sortie de la fonction de perte) des données de validation avec TensorBoard, des hyper paramètres (taux d'apprentissage $ r $) (Nombre d'époques) est réglé. Pour plus d'informations sur TensorBoard, veuillez consulter Officiel. Pour plus de simplicité, cet article ajuste un seul nombre valide. De plus, bien que les détails soient omis, des expériences préliminaires ont été menées sur le nombre de couches cachées (2), la fonction d'activation des couches cachées (ReLU), l'écart type de la distribution initiale des variables (0,01) et l'algorithme d'optimisation (Adam). Il a été facilement ajusté avec.
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))
Pour le jeu de données 1, examinons la précision et la perte des données de validation avec TensorBoard.
tensorboard --logdir=./logs/dataset=1/val
L'axe horizontal représente le nombre d'époques. À partir de là, recherchez $ r $ et $ epoch $ qui minimisent la perte de validation.
Pour le jeu de données 1, $ r = 0,0003 $ et $ epoch = 2000 $ semblent être bons. Faites de même pour le jeu de données 2 et le jeu de données 3.
Pour le Dataset 2, $ r = 0,0005 $ et $ epoch = 2000 $ semblent être bons.
Pour le jeu de données 3, $ r = 0,0001 $ et $ epoch = 8000 $ semblent être bons.
Pour chaque jeu de données, entraînez-vous avec les hyper paramètres ajustés ci-dessus et enregistrez le modèle.
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'])
Évaluez les performances avec 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')
Même s'il est classé au hasard, cela devrait être $ acc = 0,5 $, donc c'était un résultat subtil ... Heureusement, j'ai pu confirmer les effets du filtre et de l'augmentation.
Examinons un peu plus les résultats du modèle 3 le plus performant (filtré + augmenté).
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()
La figure ci-dessus montre la relation entre la période de sérialisation réelle et la sortie du classificateur. Le bleu est une œuvre correctement classée (Vrai) et le rouge est une œuvre mal classée (Faux). Les étoiles sont des œuvres classées comme des œuvres de courte durée (positives) et les cercles sont des œuvres classées comme des œuvres continues (négatives). On considère que la performance de classification est meilleure car il y a beaucoup d'œuvres bleues et la distribution est concentrée de la partie supérieure gauche vers la partie inférieure droite du graphique.
Tout d'abord, je crains qu'il n'y ait pas de sortie de 0,75 ou plus. L'apprentissage ne se passe-t-il pas bien? Ce n'est pas bien compris…. La prochaine chose dont vous devez vous soucier est le faux positif dans le coin supérieur droit du graphique. Certaines œuvres populaires sérialisées pendant plus de 100 semaines ont été classées à tort comme des œuvres de courte durée. Par conséquent, comparons l'ordre de publication (le plus mauvais) des œuvres représentatives de chaque résultat de classification.
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('Partie de Vrai positif (travail de courte durée correctement classé)')
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('Partie de Faux positif (un travail de continuation mal classé comme un travail de courte durée)')
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('Partie de Faux négatif (travail de courte durée mal classé comme travail de continuation)')
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('Partie de True négatif (travail de continuation correctement classé)')
plt.legend()
L'axe horizontal correspond à la semaine de publication et l'axe vertical correspond à l'ordre de publication à compter de la fin du livre. La légende montre le titre de l'œuvre (période de sérialisation, valeur de sortie). On peut voir que les œuvres avec Faux positif (en haut à droite) ont une tendance à la baisse plus forte dans l'ordre de publication jusqu'à la 7e semaine que les œuvres avec Vrai négatif (en bas à droite). À l'inverse, l'œuvre Faux positif (en haut à droite) peut être considérée comme une œuvre populaire qui a remonté l'infériorité dans les premiers stades. De plus, l'ordre de publication des œuvres en faux négatif (en bas à gauche) jusqu'à 7 semaines a une légère tendance à la baisse, et du moins à mes yeux, il est impossible de le distinguer de celui des œuvres en vrai négatif (en bas à droite). Je peux comprendre la raison de l'erreur de classification.
Ci-dessous, à titre de référence, les valeurs de sortie des 100 œuvres sont tracées.
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))
L'axe horizontal représente la valeur de sortie. Les parenthèses à côté du titre de l'œuvre indiquent la période de sérialisation. Le bleu indique le résultat de classification correct et le rouge indique le résultat de classification incorrect. Plus la valeur de sortie est proche de 1, plus elle est considérée comme une œuvre de courte durée.
En fait, cet article se positionne comme le résultat de ce que j'ai appris dans Deep learning foundation nanodegree [^ nd101]. J'ai commencé à écrire. C'est pourquoi je suis obstinément collé au Perceptron multicouche. Après tout, appliquer l'apprentissage automatique à des problèmes du monde réel est vraiment difficile. Sans ce thème, j'aurais été absolument frustré.
Les performances finales ont été décevantes, mais il était bon de voir les effets du filtrage et de l'augmentation de l'ensemble de données. Je pense que les performances s'amélioreront un peu plus si vous ajustez les hyper paramètres (n_drop
, n_aug
) qui ont été décidés cette fois. Alternativement, comme vous l'avez souligné dans Pourquoi les réseaux neuronaux ne peuvent pas battre SVM, vous pouvez appliquer d'autres méthodes d'apprentissage automatique telles que SVM. Peut être. Je suis épuisé donc je ne le ferai pas.
Depuis la sortie de la première partie, nous avons reçu des commentaires de nombreuses personnes, réelles et en ligne. Tout est question de programmeurs du dimanche. J'espère travailler avec vous à l'avenir. Merci d'avoir lu jusqu'au bout!
[^ nd101]: Je suis un soi-disant étudiant de mars. Je vous remercie.
En créant cet article, j'ai fait référence à ce qui suit. Merci beaucoup! : arc:
Recommended Posts