[PYTHON] Prédire les travaux de courte durée de Weekly Shonen Jump par apprentissage automatique (Partie 2: Apprentissage et évaluation)

1.Tout d'abord

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.

result.png

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. Construction de l'environnement

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é.

2.2 Table des matières des informations

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()

2.3 Module

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()

3. Modèle

3.1 Définition du problème

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.

contribution

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.

Travail de courte durée

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.

cdf.png

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.

3.2 Perceptron multicouche

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.

model.png

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é.

3.3 Ensemble de données

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.

dataset.png

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.

aug.png

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.

4. Mise en œuvre

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.

4.1 Paramètres du jeu de données: 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.

4.2 Construire un graphe de calcul: 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.

4.3 Apprentissage: 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)

4.4 Test: 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.

5. Expérience

5.1 Réglage des hyper paramètres

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

tensorboard.png

L'axe horizontal représente le nombre d'époques. À partir de là, recherchez $ r $ et $ epoch $ qui minimisent la perte de validation.

dataset1.png

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.

dataset2.png

Pour le Dataset 2, $ r = 0,0005 $ et $ epoch = 2000 $ semblent être bons.

dataset3.png

Pour le jeu de données 3, $ r = 0,0001 $ et $ epoch = 8000 $ semblent être bons.

5.2 Apprentissage

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'])

5.3 Évaluation

É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') 
result.png

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.

5.4 Considération

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()
scatter.png

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()
worsts.png

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))
output.png

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.

6. Conclusion

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.

Les références

En créant cet article, j'ai fait référence à ce qui suit. Merci beaucoup! : arc:

  1. Dessinez le japonais avec matplotlib sur Ubuntu: À propos de la sortie japonaise
  2. Application de l'apprentissage en profondeur aux problèmes du monde réel --Merantix: lorsque l'étiquette de données est biaisée À propos de la méthode d'adaptation
  3. Notes sur la méthode de propagation des erreurs de retour: À propos du perceptron multicouche en général
  4. Pourquoi avons-nous besoin de séparer les ensembles de validation et de test pour l'apprentissage supervisé? : Gestion de divers ensembles de données
  5. Ian Goodfellow et Yoshua Bengio et Aaron Courville, Deep Learning, MIT Press, 2016: Augmentation de l'ensemble de données en général (section 7.4)
  6. Comment augmenter le nombre d'images de jeux de données d'apprentissage automatique: À propos de l'augmentation des jeux de données pour les données d'image
  7. Je ne suis ni programmeur ni scientifique des données, mais j'ai touché à Tensorflow pendant un mois, donc c'est super facile à comprendre: À propos de TensorFlow
  8. TensorBoard: À propos de l'ajustement des hyper paramètres à l'aide de TensorBoard
  9. Pourquoi Neural Net ne peut pas battre SVM: Future Research Policy

Recommended Posts

Prédire les travaux de courte durée de Weekly Shonen Jump par apprentissage automatique (Partie 2: Apprentissage et évaluation)
Prédire les travaux de courte durée de Weekly Shonen Jump par apprentissage automatique (Partie 1: Analyse des données)
Classification des images de guitare par apprentissage automatique Partie 1
Classification des images de guitare par apprentissage automatique, partie 2
Prédire la présence ou l'absence d'infidélité par l'apprentissage automatique
Importance de l'apprentissage automatique et de l'apprentissage par mini-lots
[Apprentissage automatique] Résumé et exécution de l'évaluation / des indicateurs du modèle (avec jeu de données Titanic)
Prédire la demande de puissance avec l'apprentissage automatique, partie 2
Méthode d'évaluation du problème de régression d'apprentissage automatique (erreur quadratique moyenne et coefficient de décision)
Apprentissage automatique: reconnaissance d'image de MNIST à l'aide de PCA et de Gaussian Native Bayes
J'ai essayé de prédire la présence ou l'absence de neige par apprentissage automatique.
Mémo d'apprentissage automatique d'un ingénieur débutant Partie 1
Tournoi Numerai - Fusion de quants traditionnels et apprentissage automatique -
Apprentissage parallèle du deep learning par Keras et Kubernetes
Résumé des fonctions d'évaluation utilisées dans l'apprentissage automatique
Analyse de l'utilisation de l'espace partagé par l'apprentissage automatique
[Français] scikit-learn 0.18 Introduction de l'apprentissage automatique par le didacticiel scikit-learn
Mémo d'apprentissage automatique d'un ingénieur débutant Partie 2
Estimation raisonnable du prix de Mercari par apprentissage automatique
Mémo d'apprentissage Python pour l'apprentissage automatique par Chainer chapitres 1 et 2
Prédire le sexe des utilisateurs de Twitter grâce à l'apprentissage automatique
Une méthode concrète pour prédire les courses de chevaux et simuler le taux de récupération par apprentissage automatique
J'ai essayé de vérifier la classification yin et yang des membres hololive par apprentissage automatique
Vérification des performances du prétraitement des données pour l'apprentissage automatique (données numériques) (partie 2)
Apprentissage automatique pour apprendre avec Nogisaka 46 et Keyakizaka 46 Partie 1 Introduction
Vérification des performances du prétraitement des données pour l'apprentissage automatique (données numériques) (partie 1)
Utilisation des données ouvertes de Data City Sabae pour prédire la valeur de la jauge de niveau d'eau par apprentissage automatique Partie 2