[PYTHON] Estimation de l'auteur à l'aide du réseau neuronal et de Doc2Vec (Aozora Bunko)

introduction

J'ai fait la tâche d'estimer l'auteur en utilisant le travail tiré de l'Aozora Bunko, donc je l'ai écrit comme un article. Le code est disponible ici [https://github.com/minnsou/aozora_pred).

Le flux que j'ai fait cette fois est le suivant.

  1. Téléchargez le texte d'Aozora Bunko en utilisant wget
  2. Utilisez MeCab pour formater le texte
  3. Vectorisez le texte à l'aide de Doc2Vec (Créez des données avec le vecteur créé à partir du texte en x et l'ID de l'auteur en y)
  4. Construisez un réseau neuronal en utilisant des keras et apprenez sous supervision comme problème de classification

environnement

La bibliothèque utilise principalement BeautifulSoup, keras, Mecab, gensim. Puisqu'il s'écarte du point principal, je vais omettre ces méthodes d'installation. Fondamentalement, «pip» était plutôt bon.

Préparation

Tout d'abord, décidez qui téléchargera l'œuvre. Cette fois, pour le moment, nous avons ciblé les personnes qui satisfont «__ l'auteur de la ligne » et « le nombre d'œuvres publiées est de 20 ou plus __».

Obtenez le author ID requis pour télécharger le travail à partir de Aozora Bunko Writer List. Par exemple, Ryunosuke Akutagawa est 879.

スクリーンショット 2020-01-10 14.29.29.png

Créez «auteurs.txt» qui résume ces derniers. J'aurais pu créer cela automatiquement, mais comme le nombre de personnes était petit, je l'ai fait manuellement.

authors.txt


Ryunosuke Akutagawa 879
Takeo Arishima 25
Andersenhans Christian 19
Ishikawa Tatsuki 153
Jun Ishihara 1429
Izumi Kyoka 50
Mansaku Itami 231
Sachio Ito 58
Noeda Ito 416
Satoshi Ueda 235
Uemura Matsuzono 355
Uchida Rouan 165
Juzo Unno 160
Ranpo Edogawa 1779
Yu Okubo 10
Shigenobu Okuma 1879
Katsura Omachi 237
Asajiro Oka 1474
Kanoko Okamoto 76
Okamoto Kido 82
Miaki Ogawa 1475
Hideo Oguma 124
Oguri Mushitaro 125
Sakunosuke Oda 40
Nobuo Origuchi 933

25 personnes au total. Il y a un espace demi-largeur entre le nom et l'ID de l'auteur. De plus, il y a une personne (Yoko Okura) qui a été omise de cette liste en raison du problème décrit plus loin.

Le reste importe les bibliothèques requises. Les scripts python suivants sont les mêmes que ceux publiés sur author_prediction.ipynb.

from bs4 import BeautifulSoup
import re
import MeCab
from gensim.models.doc2vec import Doc2Vec
from gensim.models.doc2vec import TaggedDocument
import numpy as np
import matplotlib.pyplot as plt
from keras import layers
from keras import models
from keras import optimizers
from keras.utils import np_utils

Ceci termine la préparation.

1. Téléchargez le texte d'Aozora Bunko en utilisant wget

1.1 Acquisition de l'ID de travail

Tout d'abord, utilisez author.txt pour obtenir l'ID de travail de chaque auteur. Enregistrez-le sous personID ??. Txt (` ?? ʻest l'ID de l'auteur).

# authors.Basé sur txt, wget et personID avec identifiant professionnel??.Générer un txt(??L'identifiant de la personne est-il)
# personID_Mettre personID dans la liste

personID_list = []
memo = open('./authors.txt')
for line in memo:
    line = line.rstrip()
    line = line.split( )
    #print(line)
    author = line[0]
    personID = line[1]
    personID_list.append(personID)
    
    # authors.Wget l'index basé sur le personID de txt (il n'est pas nécessaire de le faire car il a déjà été créé)
    #!wget https://www.aozora.gr.jp/index_pages/person{personID}.html -O ./data/index{personID}.html
    #!sleep 1
    
    #Index enregistré??.Ouvrez le HTML
    with open("./data/index{}.html".format(personID), encoding="utf-8") as f:
        soup = BeautifulSoup(f)
        ol = soup.find("ol").text
        bookID = re.findall('ID:[0-9]*', ol) # index??.Obtenez la partie où l'ID de travail est écrit à partir du html
        #print(bookID)
        bookID_list = []
        for b in bookID:
            b = b[3:] # 'ID:'Effacer
            bookID_list.append(b) #Ajouter un identifiant professionnel
        #print(bookID_list)
        
        print('author {}\tpersonID {}\tnumber of cards {}'.format(author, personID, len(bookID_list)))
        
        # bookID_Créez un fichier texte qui décrit l'ID de travail d'un certain auteur en fonction de la liste (il n'est pas nécessaire de le faire car il a déjà été créé)
        #with open('./data/personID{}.txt'.format(personID), mode='w') as f:
        #    for b in bookID_list:
        #        f.write(b + ' ')

Lorsque vous l'exécutez, vous obtiendrez une sortie comme celle-ci. Cela créera un fichier appelé «personID ??. Txt» pour 25 personnes («??» est l'ID de l'auteur).

auteur Ryunosuke Akutagawa personID 879 nombre de cartes 376
auteur Takeo Arishima personID 25 nombre de cartes 44
auteur Andersenhans Christian personID 19 nombre de cartes 23
auteur Ishikawa Tatsuki personID 153 nombre de cartes 78
auteur Jun Ishihara personID 1429 nombre de cartes 24
auteur Izumi Kyoka personID 50 nombre de cartes 208
auteur Mansaku Itami personID 231 nombre de cartes 23
auteur Sachio Ito personID 58 nombre de cartes 39
auteur Ito Noeda personID 416 nombre de cartes 80
auteur Satoshi Ueda personID 235 nombre de cartes 53
auteur Uemura Matsuzono personID 355 nombre de cartes 83
auteur Uchida Roan personID 165 nombre de cartes 26
auteur Juzo Unno personID 160 nombre de cartes 177
auteur Ranpo Edogawa personID 1779 nombre de cartes 91
auteur Yu Okubo personID 10 nombre de cartes 68
auteur Shigenobu Okuma personID 1879 nombre de cartes 31
auteur Katsura Omachi personID 237 nombre de cartes 60
auteur Asajiro Oka personID 1474 nombre de cartes 25
auteur Kanoko Okamoto personID 76 nombre de cartes 119
auteur Okamoto Kido personID 82 nombre de cartes 247
auteur Miaki Ogawa personID 1475 nombre de cartes 521
auteur Hideo Oguma personID 124 nombre de cartes 33
auteur Oguri Mushitaro personID 125 nombre de cartes 22
auteur Sakunosuke Oda personID 40 nombre de cartes 70
auteur Nobuo Origuchi personID 933 nombre de cartes 197

Mise en garde

Dans le script ci-dessus, si vous supprimez # et créez vous-même wget pour créer personID ??. Txt, vous en obtiendrez un avec plus d'identifiants de travail que le personID ??. Txt téléchargé. Je vais. En effet, l'ID de travail qui donne une erreur __ lors de l'extraction du texte avec le script suivant est supprimé manuellement __.

Par exemple, dans l'oeuvre «Apple Pie» de Yu Okubo, il y a un lien externe en plus du site habituel d'Aozora Bunko. Il est collé et je l'obtiendrai. J'aimerais pouvoir obtenir ce site de bibliothèque de ciel bleu, mais [site externe](http: //p.booklog. jp / book / 35337) sera prise. De plus, comme le [Short Songbook] de Hideo Oguma (https://www.aozora.gr.jp/cards/000124/card651.html), le texte n'existe pas (<div class =" main_text "> Certains n'ont pas de balises), ce qui provoque également une erreur.

J'ai téléchargé le personID ??. Txt supprimé pour un identifiant de travail aussi exceptionnel, donc si vous voulez le déplacer pour le moment, il est prudent d'éviter de décommenter __. Seuls ceux qui souhaitent vérifier l'opération doivent décommenter et modifier le répertoire de destination de l'enregistrement __.

1.2 Enregistrez votre travail sous wget

Ensuite, téléchargez le travail en utilisant l'ID de travail écrit dans personID ??. Txt. J'utilise pubserver2 pour télécharger le travail, mais maintenant que j'y pense, pourrais-je simplement faire wget sans passer par pubserver2?

# personID??.Obtenez l'ID de travail de txt et apportez le travail avec wget (jusqu'à 50 œuvres par auteur)
#Texte_x_y.html(x est personID, y est bookID)

for personID in personID_list:
    print('personID', personID)
    with open("./data/personID{}.txt".format(personID), encoding="utf-8") as f:
        for bookID_str in f:
            bookID_list = bookID_str.split( )
            print('number of cards', len(bookID_list))
            
            #S'il y a trop d'œuvres, cela prendra du temps, alors limitez-vous à 50 œuvres
            if len(bookID_list) >= 50:
                bookID_list = bookID_list[:50]
            for bookID in bookID_list:
                print('ID', bookID)
                
                #Créez un html qui décrit le texte en wgetting basé sur le bookID (il n'est pas nécessaire de le faire car il a déjà été créé)
                #!wget http://pubserver2.herokuapp.com/api/v0.1/books/{bookID}/content?format=html -O ./data/text{personID}_{bookID}.html
                #!sleep 1

2. Utilisez MeCab pour formater le texte

Créez une fonction pour formater et étiqueter le texte. Ceci est basé sur ici. Les balises sont numérotées de 0 à 24 dans l'ordre «auteurs.txt» (Ryunosuke Akutagawa vaut 0, Takeo Arishima vaut 1, ..., Nobuo Origuchi vaut 24). Si vous utilisez l'identifiant de l'auteur tel qu'il est pour le numéro de tag, cela sera gênant lors de la génération des données, nous allons donc le renuméroter ici.

# doc(Le texte de l'oeuvre)Pour lister les mots avec uniquement des verbes, des adjectifs et une nomenclature
#Générer un document balisé composé de mots et de balises

def split_into_words(doc, name=''):
    mecab = MeCab.Tagger("-Ochasen")
    lines = mecab.parse(doc).splitlines() #Analyse morphologique
    words = []
    for line in lines:
        chunks = line.split('\t')
        #Ajouter uniquement la nomenclature (à l'exclusion des nombres), des verbes et des adjectifs
        if len(chunks) > 3 and (chunks[3].startswith('verbe') or chunks[3].startswith('adjectif') or (chunks[3].startswith('nom') and not chunks[3].startswith('nom-nombre'))):
            words.append(chunks[0])
    #print(words)
    return TaggedDocument(words=words, tags=[name])

Générez train_text avec les données d'entraînement et test_text avec les données d'évaluation.

#Former pour apprendre_Générer du texte (fixé à 20 œuvres par auteur)
#Test pour tester_Créez également du texte (utilisez tout le reste des données non utilisées dans l'apprentissage)
#Puisque le nombre d'œuvres varie d'une personne à l'autre, testez_Le nombre d'œuvres incluses dans le texte varie également d'une personne à l'autre

train_text = []
test_text = []

for i, personID in enumerate(personID_list):
    print('personID', personID)

    with open("./data/personID{}.txt".format(personID), encoding="utf-8") as f:
        for bookID_str in f:
            #print(bookID)
            bookID_list = bookID_str.split( )
            
            #Je n'ai pas téléchargé plus de 50 œuvres, donc je l'ai coupé
            if len(bookID_list) >= 50:
                bookID_list = bookID_list[:50]
            print('number of cards', len(bookID_list))
            
            for j, bookID in enumerate(bookID_list):
                
                #Ouvrez le html qui contient le corps que vous avez enregistré précédemment
                soup = BeautifulSoup(open("./data/text{}_{}.html".format(personID, bookID), encoding="shift_jis"))

                #Le texte est écrit<div>Sortir
                main_text = soup.find("div", "main_text").text
                #print(main_text)
                
                #Les 20 premières œuvres sont en train_Mettez-le dans le texte, le reste est test_Mettre du texte
                if j < 20:
                    train_text.append(split_into_words(main_text, str(i)))
                    print('bookID\t{}\ttrain'.format(bookID))
                else:
                    test_text.append(split_into_words(main_text, str(i)))
                    print('bookID\t{}\ttest'.format(bookID))

3. Vectorisez le texte à l'aide de Doc2Vec

Créez un modèle pour Doc2Vec. Les hyper paramètres tels que alpha et les époques sont assez bien définis.

#Créer et entraîner un modèle de Doc2Vec
model = Doc2Vec(vector_size=len(train_text), dm=0, alpha=0.05, min_count=5)
model.build_vocab(train_text)
model.train(train_text, total_examples=len(train_text), epochs=5)

#Enregistrer les résultats d'apprentissage
#model.save('./data/doc2vec.model')

Créez des données en convertissant le texte en vecteur numérique pour l'entraînement avec un réseau neuronal.

#Créez des données pour le réseau neuronal à partir du modèle et du texte créés, qui est une liste de documents étiquetés.
def text2xy(model, text):
    x = []
    y = []
    for i in range(len(text)):
        #print(i)
        vec = model.infer_vector(text[i].words) #Convertir en un vecteur de nombres
        x.append(vec.tolist())
        y.append(int(text[i].tags[0]))

    x = np.array(x)
    y = np_utils.to_categorical(y) #Convertir les numéros d'étiquette en onehot
    return x, y

#Création de données d'entraînement et de données d'évaluation
x_train, y_train = text2xy(model, train_text)
x_test, y_test = text2xy(model, test_text)

4. Construisez un réseau neuronal à l'aide de keras et apprenez sous supervision comme problème de classification

Préparez une fonction (dense_train) qui fonctionne de la création du modèle à la formation et une fonction (draw_acc et draw_loss) pour le dessin.

Le modèle du réseau neuronal que j'ai créé est un modèle assez simple composé de trois couches entièrement connectées.

Pour la fonction de perte, nous avons utilisé l'entropie croisée catégorielle, qui est souvent utilisée dans les problèmes de classification. Le nombre d'unités et le taux d'apprentissage sont appropriés.

def dense_train(epochs):    
    #Définition du modèle
    kmodel = models.Sequential()
    kmodel.add(layers.Dense(512, activation='relu', input_shape=(500,)))
    kmodel.add(layers.Dense(256, activation='relu'))
    kmodel.add(layers.Dense(25, activation='softmax'))
    kmodel.summary()

    #Compiler le modèle
    kmodel.compile(loss='categorical_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
    
    #Apprentissage de modèle
    history = kmodel.fit(x=x_train, y=y_train, epochs=epochs, validation_data=(x_test, y_test))

    #Enregistrer le modèle
    #model.save('./data/dense.h5')
    return history, kmodel

#Graphique du taux de réponse correct
def draw_acc(history):
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    epochs = range(1, len(acc) + 1)

    fig = plt.figure()
    fig1 = fig.add_subplot(111)
    fig1.plot(epochs, acc, 'bo', label='Training acc')
    fig1.plot(epochs, val_acc, 'b', label='Validation acc')

    fig1.set_xlabel('epochs')
    fig1.set_ylabel('accuracy')
    fig.legend(bbox_to_anchor=(0., 0.19, 0.86, 0.102), loc=5) #Le deuxième argument d'ancre (légende) est y, le troisième argument est x

    #Enregistrer l'image
    fig.savefig('./acc.pdf')
    plt.show()

#tracé des pertes
def draw_loss(history):
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(loss) + 1)
    
    fig = plt.figure()
    fig1 = fig.add_subplot(111)
    fig1.plot(epochs, loss, 'bo', label='Training loss')
    fig1.plot(epochs, val_loss, 'b', label='Validation loss')

    fig1.set_xlabel('epochs')
    fig1.set_ylabel('loss')
    fig.legend(bbox_to_anchor=(0., 0.73, 0.86, 0.102), loc=5) #Le deuxième argument d'ancre (légende) est y, le troisième argument est x

    #Enregistrer l'image
    #fig.savefig('./loss.pdf')
    plt.show()

Former et dessiner un graphique du taux de réponse correcte. Cette fois, le nombre d'époques était de 10.

history, kmodel = dense_train(10)
draw_acc(history)

Le résultat obtenu est le suivant.

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 512)               256512    
_________________________________________________________________
dense_2 (Dense)              (None, 256)               131328    
_________________________________________________________________
dense_3 (Dense)              (None, 25)                6425      
=================================================================
Total params: 394,265
Trainable params: 394,265
Non-trainable params: 0
_________________________________________________________________
Train on 500 samples, validate on 523 samples
Epoch 1/10
500/500 [==============================] - 0s 454us/step - loss: 3.0687 - acc: 0.1340 - val_loss: 2.9984 - val_acc: 0.3308
Epoch 2/10
500/500 [==============================] - 0s 318us/step - loss: 2.6924 - acc: 0.6300 - val_loss: 2.8255 - val_acc: 0.5698
Epoch 3/10
500/500 [==============================] - 0s 315us/step - loss: 2.3527 - acc: 0.8400 - val_loss: 2.6230 - val_acc: 0.6864
Epoch 4/10
500/500 [==============================] - 0s 283us/step - loss: 1.9961 - acc: 0.9320 - val_loss: 2.4101 - val_acc: 0.7610
Epoch 5/10
500/500 [==============================] - 0s 403us/step - loss: 1.6352 - acc: 0.9640 - val_loss: 2.1824 - val_acc: 0.8088
Epoch 6/10
500/500 [==============================] - 0s 237us/step - loss: 1.2921 - acc: 0.9780 - val_loss: 1.9504 - val_acc: 0.8337
Epoch 7/10
500/500 [==============================] - 0s 227us/step - loss: 0.9903 - acc: 0.9820 - val_loss: 1.7273 - val_acc: 0.8432
Epoch 8/10
500/500 [==============================] - 0s 220us/step - loss: 0.7424 - acc: 0.9840 - val_loss: 1.5105 - val_acc: 0.8642
Epoch 9/10
500/500 [==============================] - 0s 225us/step - loss: 0.5504 - acc: 0.9840 - val_loss: 1.3299 - val_acc: 0.8623
Epoch 10/10
500/500 [==============================] - 0s 217us/step - loss: 0.4104 - acc: 0.9840 - val_loss: 1.1754 - val_acc: 0.8719

Cliquez ici pour un diagramme des résultats d'apprentissage.

Unknown.png

Après 6 époques, j'ai l'impression d'être malade, mais au final, j'ai obtenu un taux de réponse correcte de __87,16% __. Même si je n'ai pas fait beaucoup de réglages d'hyper-paramètres, j'ai l'impression d'avoir un pourcentage décent de réponses correctes.

À la fin

Réglage des hyper paramètres et sélection du modèle (utilisez LSTM ou quelque chose?) Peut être fait plus. Veuillez commenter si vous avez des suggestions. Merci pour votre visite.

Recommended Posts

Estimation de l'auteur à l'aide du réseau neuronal et de Doc2Vec (Aozora Bunko)
Implémentation simple d'un réseau neuronal à l'aide de Chainer
Réseau neuronal avec OpenCV 3 et Python 3
Théorie et implémentation simples des réseaux neuronaux
Prédiction des survivants à l'aide du réseau neuronal titanesque de Kaggle [80,8%]
Essayez d'utiliser TensorFlow-Part 2-Convolution Neural Network (MNIST)
Implémentation de réseaux neuronaux "flous" avec Chainer
Implémentation de réseau neuronal simple à l'aide de la préparation Chainer-Data-
Clustering des livres d'Aozora Bunko avec Doc2Vec
Implémentation de réseau neuronal simple à l'aide de la description du modèle Chainer-
2. Écart moyen et standard avec le réseau neuronal!
Implémentation simple d'un réseau de neurones à l'aide de Chainer ~ Définition d'un algorithme d'optimisation ~
Estimation de l'orientation de la tête avec Python et OpenCV + dlib
Apprentissage par renforcement 10 Essayez d'utiliser un réseau neuronal formé.
Une autre méthode de conversion de style utilisant le réseau neuronal convolutif
Analyse du réseau des acteurs vocaux (à l'aide de word2vec et networkx) (1/2)
Analyse du réseau des acteurs vocaux (utilisant word2vec et networkx) (2/2)