[PYTHON] Autorenschätzung unter Verwendung von neuronalen Netzen und Doc2Vec (Aozora Bunko)

Einführung

Ich habe die Aufgabe, den Autor anhand der Arbeit aus dem Aozora Bunko zu schätzen, und habe sie als Artikel geschrieben. Der Code ist hier verfügbar [https://github.com/minnsou/aozora_pred].

Der Fluss, den ich diesmal gemacht habe, ist wie folgt.

  1. Laden Sie den Text von Aozora Bunko mit wget herunter
  2. Verwenden Sie MeCab, um den Text zu formatieren
  3. Vektorisieren Sie den Text mit Doc2Vec (Erstellen Sie Daten mit dem Vektor, der aus dem Text als x und der Autoren-ID als y erstellt wurde.)
  4. Konstruieren Sie ein neuronales Netz mit Keras und lernen Sie unter Aufsicht als Klassifizierungsproblem

Umgebung

Die Bibliothek verwendet hauptsächlich BeautifulSoup, Keras, Mecab, Gensim. Ich werde diese Installationsmethoden weglassen, da sie vom Hauptpunkt abweichen. Grundsätzlich war pip ziemlich gut.

Vorbereitung

Entscheiden Sie zunächst, wer die Arbeit herunterladen soll. Dieses Mal haben wir uns vorerst an Personen gewandt, die "__ den Autor der Zeile " und " die Anzahl der veröffentlichten Werke 20 oder mehr __" erfüllen.

Holen Sie sich die author ID, die zum Herunterladen der Arbeit von Aozora Bunko Writer List erforderlich ist. Zum Beispiel ist Ryunosuke Akutagawa 879.

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

Erstellen Sie "autors.txt", die diese zusammenfasst. Es wäre schön gewesen, dies automatisch zu erstellen, aber da die Anzahl der Personen gering war, habe ich es manuell erstellt.

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

Insgesamt 25 Personen. Zwischen dem Namen und der Autoren-ID befindet sich ein halbes Feld. Darüber hinaus gibt es eine Person (Yoko Okura), die aufgrund des später beschriebenen Problems aus dieser Liste gestrichen wurde.

Der Rest importiert die erforderlichen Bibliotheken. Nachfolgende Python-Skripte sind dieselben wie die unter author_prediction.ipynb veröffentlichten.

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

Damit ist die Vorbereitung abgeschlossen.

1. Laden Sie den Text von Aozora Bunko mit wget herunter

1.1 Erwerb des Arbeitsausweises

Verwenden Sie zunächst author.txt, um die Arbeits-ID für jeden Autor abzurufen. Speichern Sie dies als personID ??. Txt (??ist die Autoren-ID).

# authors.Basierend auf txt, wget und personID mit Arbeits-ID??.Generiere txt(??Ist die Personen-ID)
# personID_Setzen Sie personID in die Liste ein

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 den Index basierend auf der personID von txt (es ist nicht notwendig, dies zu tun, da es bereits erstellt wurde)
    #!wget https://www.aozora.gr.jp/index_pages/person{personID}.html -O ./data/index{personID}.html
    #!sleep 1
    
    #Gespeicherter Index??.Öffnen Sie 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??.Holen Sie sich den Teil, in dem die Arbeits-ID geschrieben ist, aus dem HTML-Code
        #print(bookID)
        bookID_list = []
        for b in bookID:
            b = b[3:] # 'ID:'Löschen
            bookID_list.append(b) #Arbeits-ID hinzufügen
        #print(bookID_list)
        
        print('author {}\tpersonID {}\tnumber of cards {}'.format(author, personID, len(bookID_list)))
        
        # bookID_Erstellen Sie eine Textdatei, die die Arbeits-ID eines bestimmten Autors basierend auf der Liste beschreibt (dies ist nicht erforderlich, da sie bereits erstellt wurde).
        #with open('./data/personID{}.txt'.format(personID), mode='w') as f:
        #    for b in bookID_list:
        #        f.write(b + ' ')

Wenn Sie es ausführen, erhalten Sie eine Ausgabe wie diese. Dadurch wird eine Datei mit dem Namen personID ??. Txt für 25 Personen erstellt (??ist die Autoren-ID).

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

Hinweis

Wenn Sie im obigen Skript "#" entfernen und "wget" selbst erstellen, um "personID ??. Txt" zu erstellen, erhalten Sie eine mit mehr Arbeits-IDs als die hochgeladene "personID ??. Txt`. Ich werde. Dies liegt daran, dass die Arbeits-ID, die beim Extrahieren des Texts mit dem folgenden Skript einen __ Fehler verursacht, manuell gelöscht wird __.

Zum Beispiel gibt es in der Arbeit "Apple Pie" von Yu Okubo zusätzlich zu der üblichen Aozora Bunko-Site einen externen Link. Es ist eingefügt und ich werde es bekommen. Ich wünschte, ich könnte diese Blue Sky Library Site bekommen, aber [externe Site](http: //p.booklog. jp / book / 35337) wird genommen. Ebenso wie Hideo Ogumas Short Songbook existiert der Text nicht (<div class =" main_text "> Einige haben keine Tags), was ebenfalls einen Fehler verursacht.

Ich habe die gelöschte personID ??. Txt für eine so außergewöhnliche Arbeits-ID hochgeladen. Wenn Sie sie also vorerst verschieben möchten, können Sie das Kommentieren von __ vermeiden. Nur diejenigen, die den Vorgang überprüfen möchten, sollten das Speicherverzeichnis __ auskommentieren und ändern.

1.2 Speichern Sie Ihre Arbeit als "wget"

Laden Sie als Nächstes die Arbeit mit der in personID ??. Txt geschriebenen Arbeits-ID herunter. Ich verwende pubserver2, um die Arbeit herunterzuladen, aber jetzt, wo ich darüber nachdenke, könnte ich einfach "wget" machen, ohne pubserver2 zu durchlaufen?

# personID??.Holen Sie sich die Arbeits-ID von txt und bringen Sie die Arbeit mit wget (bis zu 50 Werke pro Autor)
#Der Name des HTML-Codes, in dem die Arbeit geschrieben ist, ist Text_x_y.html(x ist personID, y ist 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))
            
            #Wenn es zu viele Werke gibt, wird es einige Zeit dauern, also beschränken Sie sich auf 50 Werke
            if len(bookID_list) >= 50:
                bookID_list = bookID_list[:50]
            for bookID in bookID_list:
                print('ID', bookID)
                
                #Erstellen Sie eine HTML-Datei, die den Text beschreibt, indem Sie anhand der bookID aufrufen (dies ist nicht erforderlich, da er bereits erstellt wurde).
                #!wget http://pubserver2.herokuapp.com/api/v0.1/books/{bookID}/content?format=html -O ./data/text{personID}_{bookID}.html
                #!sleep 1

2. Verwenden Sie MeCab, um den Text zu formatieren

Erstellen Sie eine Funktion zum Formatieren und Kennzeichnen des Texts. Dies basiert auf hier. Den Tags werden Nummern von 0 bis 24 in der Reihenfolge "autoren.txt" zugewiesen (Ryunosuke Akutagawa ist 0, Takeo Arishima ist 1, ..., Nobuo Origuchi ist 24). Wenn Sie die Autoren-ID wie für die Tag-Nummer verwenden, ist dies beim Generieren von Daten problematisch. Daher werden wir sie hier neu nummerieren.

# doc(Der Text der Arbeit)Wörter nur mit Verben, Adjektiven und Nomenklatur auflisten
#Generieren Sie ein mit Tags versehenes Dokument, das aus Wörtern und Tags besteht

def split_into_words(doc, name=''):
    mecab = MeCab.Tagger("-Ochasen")
    lines = mecab.parse(doc).splitlines() #Morphologische Analyse
    words = []
    for line in lines:
        chunks = line.split('\t')
        #Fügen Sie nur Nomenklaturen (ohne Zahlen), Verben und Adjektive hinzu
        if len(chunks) > 3 and (chunks[3].startswith('Verb') or chunks[3].startswith('Adjektiv') or (chunks[3].startswith('Substantiv') and not chunks[3].startswith('Substantiv-Nummer'))):
            words.append(chunks[0])
    #print(words)
    return TaggedDocument(words=words, tags=[name])

Generieren Sie train_text mit Trainingsdaten und test_text mit Auswertungsdaten.

#Trainiere um zu lernen_Text generieren (fest auf 20 Werke pro Autor)
#Test zum Testen_Erstellen Sie auch Text (verwenden Sie alle anderen Daten, die nicht zum Lernen verwendet wurden).
#Da die Anzahl der Arbeiten von Person zu Person unterschiedlich ist, testen Sie_Die Anzahl der im Text enthaltenen Werke variiert ebenfalls von Person zu Person

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( )
            
            #Ich habe nicht mehr als 50 Werke heruntergeladen, also habe ich es geschnitten
            if len(bookID_list) >= 50:
                bookID_list = bookID_list[:50]
            print('number of cards', len(bookID_list))
            
            for j, bookID in enumerate(bookID_list):
                
                #Öffnen Sie das HTML, das den zuvor gespeicherten Text enthält
                soup = BeautifulSoup(open("./data/text{}_{}.html".format(personID, bookID), encoding="shift_jis"))

                #Der Text ist geschrieben<div>Mitnahme
                main_text = soup.find("div", "main_text").text
                #print(main_text)
                
                #Die ersten 20 Werke sind Zug_Setzen Sie es in Text, der Rest ist Test_Text einfügen
                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. Vektorisieren Sie den Text mit Doc2Vec

Erstellen Sie ein Modell für Doc2Vec. Hyperparameter wie Alpha und Epochen sind ziemlich gut definiert.

#Erstellen und trainieren Sie ein Modell von 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)

#Lernergebnisse speichern
#model.save('./data/doc2vec.model')

Erstellen Sie Daten, indem Sie den Text in einen numerischen Vektor für das Training mit einem neuronalen Netz konvertieren.

#Erstellen Sie Daten für das neuronale Netz aus dem erstellten Modell und Text, einer Liste mit markierten Dokumenten.
def text2xy(model, text):
    x = []
    y = []
    for i in range(len(text)):
        #print(i)
        vec = model.infer_vector(text[i].words) #In einen Zahlenvektor konvertieren
        x.append(vec.tolist())
        y.append(int(text[i].tags[0]))

    x = np.array(x)
    y = np_utils.to_categorical(y) #Konvertieren Sie Tag-Nummern in onehot
    return x, y

#Erstellung von Trainingsdaten und Bewertungsdaten
x_train, y_train = text2xy(model, train_text)
x_test, y_test = text2xy(model, test_text)

4. Konstruieren Sie ein neuronales Netz mit Keras und lernen Sie unter Aufsicht als Klassifizierungsproblem

Bereiten Sie eine Funktion (dens_train) vor, die von der Modellerstellung bis zum Training ausgeführt wird, und eine Funktion (draw_acc und draw_loss) zum Zeichnen.

Das Modell des von mir erstellten neuronalen Netzes ist ein ziemlich einfaches Modell, das aus drei vollständig verbundenen Schichten besteht.

Für die Verlustfunktion verwendeten wir eine kategoriale Kreuzentropie, die häufig bei Klassifizierungsproblemen verwendet wird. Die Anzahl der Einheiten und die Lernrate sind angemessen.

def dense_train(epochs):    
    #Modelldefinition
    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()

    #Modell kompilieren
    kmodel.compile(loss='categorical_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
    
    #Modelllernen
    history = kmodel.fit(x=x_train, y=y_train, epochs=epochs, validation_data=(x_test, y_test))

    #Modell speichern
    #model.save('./data/dense.h5')
    return history, kmodel

#Korrektes Diagramm der Antwortrate
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) #Das zweite Argument des Ankers (Legende) ist y, das dritte Argument ist x

    #Bild speichern
    fig.savefig('./acc.pdf')
    plt.show()

#Verlustplot
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) #Das zweite Argument des Ankers (Legende) ist y, das dritte Argument ist x

    #Bild speichern
    #fig.savefig('./loss.pdf')
    plt.show()

Trainiere und zeichne ein Diagramm der richtigen Antwortrate. Diesmal betrug die Anzahl der Epochen 10.

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

Die erhaltene Ausgabe ist wie folgt.

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

Klicken Sie hier für ein Diagramm der Lernergebnisse.

Unknown.png

Nach 6 Epochen fühle ich mich krank, aber am Ende habe ich eine korrekte Antwortrate von __87,16% __. Obwohl ich nicht viel Hyperparameter-Tuning durchgeführt habe, habe ich das Gefühl, einen anständigen Prozentsatz an richtigen Antworten zu erhalten.

Am Ende

Hyperparameter-Tuning und Modellauswahl (LSTM verwenden oder so?) Kann mehr getan werden. Bitte kommentieren Sie, wenn Sie Vorschläge haben. Danke für Ihren Besuch.

Recommended Posts

Autorenschätzung unter Verwendung von neuronalen Netzen und Doc2Vec (Aozora Bunko)
Einfache Implementierung eines neuronalen Netzwerks mit Chainer
Neuronales Netzwerk mit OpenCV 3 und Python 3
Einfache Theorie und Implementierung des neuronalen Netzes
Überlebensvorhersage unter Verwendung des titanischen neuronalen Netzes von Kaggle [80,8%]
Versuchen Sie es mit TensorFlow-Part 2-Convolution Neural Network (MNIST).
Implementierung von "verschwommenen" neuronalen Netzen mit Chainer
Einfache Implementierung eines neuronalen Netzwerks mithilfe der Chainer-Datenaufbereitung
Clustering von Büchern von Aozora Bunko mit Doc2Vec
Einfache Implementierung eines neuronalen Netzwerks mithilfe der Beschreibung des Chainer-Modells
2. Mittelwert und Standardabweichung beim neuronalen Netz!
Einfache Implementierung eines neuronalen Netzwerks mit Chainer ~ Optimierungsalgorithmus einstellen ~
Schätzung der Kopforientierung mit Python und OpenCV + dlib
Verstärkungslernen 10 Versuchen Sie es mit einem trainierten neuronalen Netz.
Eine andere Stilkonvertierungsmethode unter Verwendung des Convolutional Neural Network
Netzwerkanalyse von Sprachschauspielern (mit word2vec und networkx) (1/2)
Netzwerkanalyse von Sprachschauspielern (mit word2vec und networkx) (2/2)