[Kapitel 5] Einführung in Python mit 100 Klopfen Sprachverarbeitung

Dieser Artikel ist eine Fortsetzung meines Buches Einführung in Python mit 100 Knock. Ich werde die Klasse hauptsächlich mit 100 Schläge Kapitel 5 erklären.

Lassen Sie uns zuerst CaboCha installieren und das "Ich bin eine Katze" von Aozora Bunko analysieren.

!cabocha -f1 < neko.txt > neko.txt.cabocha
!head -n15 neko.txt.cabocha
* 0 -1D 0/0 0.000000

Eine Nomenklatur, Nummer, *, *, *, *, eins, eins, eins EOS EOS * 0 2D 0/0 -0.764522 Symbol, leer, *, *, *, *, ,, * 1 2D 0/1 -0.764522 Ich Nomenklatur, Synonyme, allgemein, *, *, *, ich, Wagahai, Wagahai Ist ein Assistent, ein Assistent, *, *, *, *, ist, ha, wa * 2 -1D 0/2 0.000000 Katzennomenklatur, allgemein, *, *, *, *, Katze, Katze, Katze Hilfsverb, *, *, *, spezielle da, kontinuierliche Form, da, de, de Hilfsverb, *, *, *, 5. Dan / La-Linie Al, Grundform, aru, al, al .. Symbole, Satzzeichen, *, *, *, * ,. ,. ,. EOS

Eine Zeile wie "* Klauselnummer, Zielklauselnummer D" wurde hinzugefügt.

Klasse

Was ist objektorientierte Programmierung? Wenn Sie zum Punkt ... gehen, wird dies zu Kontroversen führen, daher werde ich mich auf die Python-Klasse konzentrieren. Ich denke, es wird einfacher, wenn Sie etwas Java kennen.

Warum Klassen verwenden?

In Bezug auf die Themen in diesem Kapitel der Satz->Phrase->Wenn Sie versuchen, die hierarchische Struktur der Morphologie ohne Verwendung von Klassen zu handhaben, (Anweisung)|Phrase|(Variable) für die Morphologie|Funktionen) werden durcheinander gebracht und es wird eine große Sache.forDer Satz ist wahrscheinlich verwirrend. Sie können Klassen verwenden, um Funktionen nach Argumenttyp zu gruppieren, und Sie können den Variablenbereich reduzieren, was die Codierung etwas vereinfacht.

Erklärung verschiedener Begriffe

Schauen wir uns ein Beispiel für den Datumsdatentyp "datetime.date" an.

import datetime
#Instanziierung
a_day = datetime.date(2022,2,22)
print(a_day)
print(type(a_day))
#Instanzvariable
print(a_day.year, a_day.month, a_day.day)
#Klassenvariable
print(datetime.date.max, a_day.max)
#Instanzmethode
print(a_day.weekday(), datetime.date.weekday(a_day))
#Klassenmethode
print(datetime.date.today())
2022-02-22
<class 'datetime.date'>
2022 2 22
9999-12-31 9999-12-31
1 1
2020-05-06

datetime.date () erstellt eine Entität (Instanz) vom Typ datetime.date und setzt (initialisiert) den Wert basierend auf dem an das Argument übergebenen Datum.

Die Instanz a_day hat instanzspezifische Werte (Attribute), die das Jahr, den Monat und den Tag darstellen. Diese werden als Instanzvariablen bezeichnet. Andererseits wird ein Wert, der allen Instanzen vom Typ "datetime.date" gemeinsam ist, als Klassenvariable bezeichnet. In diesem Beispiel ist das maximal mögliche Datum die Klassenvariable. Funktionen, die eine Instanz erfordern, werden als Instanzmethoden bezeichnet, und Funktionen, die nicht als Klassenmethoden bezeichnet werden.

Beachten Sie, dass "a_day.weekday ()" und "datetime.date.weekday (a_day)" für Instanzmethoden gleichwertig sind. Stellen Sie sich Python außerdem so vor, als würde es Ersteres in Letzteres konvertieren und ausführen. Der Rückgabewert bedeutet übrigens, dass 0 Montag und 1 Dienstag ist.

Klassendefinition

Die Datentyp-Entwurfszeichnung ist * Klasse *. Definieren wir die Klasse. Ist es schließlich leicht zu verstehen, welche Klasse ein bestimmtes Monster imitiert?

class Nezumi:
    #Klassenvariable
    types = ('Denki',)
    learnable_moves = {'Denkou Sekka', 'Kaminari', 'Denji'}
    base_hp = 35
    
    #Initialisierungsmethode
    def __init__(self, name, level):
        #Instanzvariable
        self.name = name
        self.level = level
        self.learned_moves = []
    
    #Instanzmethode
    def learn(self, move):
        if move in self.learnable_moves:
            self.learned_moves.append((move))
            print(f'{self.name}Ist neu{move}Ich erinnerte mich!')
            
    #Klassenmethode
    @classmethod
    def hatch(cls):
        nezumi = cls('Maus oder Ratte', 1)
        nezumi.learned_moves.append('Denkou Sekka')
        print('Das Ei wurde anstelle der Maus geboren!')
        return nezumi

#Instanziierung
reo = Nezumi('Löwe', 44)
#Bestätigung der Mitgliedsvariablen
print(reo.name, reo.level, reo.learned_moves, reo.types)
#Instanzmethodenaufruf
reo.learn('Kaminari')

Leo 44 [] Denki Leo hat einen neuen Kaminari gelernt!

#Klassenmethodenaufruf
nezu = Nezumi.hatch()
#Bestätigung der Instanzvariablen
print(nezu.name, nezu.level, nezu.learned_moves)

Das Ei wurde anstelle der Maus geboren! Maus 1 ['Denkou Sekka']

__init __ () ist eine Methode, die Instanzvariablen initialisiert. Es wird als Initialisierungsmethode oder Initialisierer bezeichnet. Das erste Argument, "self", repräsentiert eine Instanz. Und was in der Methode in Form von "self.variable name" definiert ist, wird zu einer Instanzvariablen.

Durch Aufrufen der definierten "Nezumi" -Klasse als "Nezumi ()" wird nach der internen Instanziierung (neu) "__init __ ()" für die erstellte Instanz "self" ausgeführt.

Die Instanz "reo" wird dem ersten Argument "self" der Instanzmethode zugewiesen. Wenn Sie wie "reo.learn (" Kaminari ")" aufrufen, wird "Nezumi.learn (reo," Kaminari ")" ausgeführt. Deshalb brauchen wir dieses "Selbst".

Klassenmethoden werden mit einem * Dekorator * namens "@ classmethod" definiert. In Klassenmethoden ist es orthodox, eine spezielle Initialisierungsmethode zu beschreiben. Das Klassenobjekt Nezumi ist dem ersten Argument cls zugeordnet. Daher ist "cls (" mouse ", 1)" dasselbe wie "Nezumi (" mouse ", 1)".

Im obigen Beispiel wird übrigens nacheinander auf die Instanzvariablen zugegriffen und diese überprüft. Wenn Sie jedoch die integrierte Funktion "vars ()" verwenden, wird die Liste im Format "dict" zurückgegeben.

Spezielle Methode

Eine Methode, die ohne Erlaubnis aufgerufen wird, wenn eine spezielle Operation / Operation ausgeführt wird, wird als spezielle Methode bezeichnet. __init __ () ist einer von ihnen. Es gibt [viele] spezielle Methoden (https://docs.python.org/ja/3/reference/datamodel.html#special-method-names), daher werde ich nicht zu sehr ins Detail gehen, aber es gibt die folgenden.

--__str__ () : Definiert die Anzeigezeichenfolge, wenn sie anprint ()usw. übergeben wird. --__repr__ () : Definiert die Anzeigezeichenfolge für das Debuggen. Sie können es sehen, indem Sie es im interaktiven Modus auswerten lassen oder an "repr ()" übergeben. --__len__ () : Definiert den Rückgabewert bei Übergabe anlen ().

Privates Mitglied

Python hat diese Funktion nicht. Es ist üblich, ihm einen Unterstrich voranzustellen, z. B. "_spam".

Erbe

Beschreibung der Vererbung selbst ist nicht so lang und erscheint manchmal im Deep-Learning-Code. In diesem Kapitel ist dies jedoch nicht erforderlich, und es gibt viele begleitende Geschichten (Verwendung, "super ()", Namespace, "statische Methode" usw.), daher werde ich sie weglassen. ~~ Ich habe ein Beispiel mitgebracht, das die Vererbung leicht zu erklären scheint. ~~

Datenklasse

Das Modul Datenklassen wurde hinzugefügt, um Klassen für die Datenaufbewahrung in Python 3.7 einfach zu definieren. Es definiert "init ()" und "repr ()" ohne Erlaubnis. Ich bin der Meinung, dass es auch bei diesem Problem verwendet werden kann, aber ich werde es weglassen, da ich mich möglicherweise an Typanmerkungen erinnere, wie sie sind.

40. Lesen des Ergebnisses der Abhängigkeitsanalyse (Morphologie)

Implementieren Sie die Klasse "Morph", die die Morphologie darstellt. Diese Klasse hat Oberflächenform (Oberfläche), Grundform ( Basis), Teiltexte (pos) und Teiltexte Unterklasse 1 ( pos1) als Mitgliedsvariablen. Lesen Sie außerdem das Analyseergebnis von CaboCha (neko.txt.cabocha), drücken Sie jeden Satz als Liste von Morph-Objekten aus und zeigen Sie die morphologische Elementzeichenfolge des dritten Satzes an.

Unten finden Sie ein Beispiel für die Antwort.

q40.py


import argparse
from itertools import groupby
import sys


class Morph:
    """Lesen Sie eine Zeile der Datei im Cabocha-Gitterformat"""
    __slots__ = ('surface', 'pos', 'pos1', 'base')
    #Mein Substantiv,Gleichbedeutend,Allgemeines,*,*,*,ich,Wagahai,Wagahai
    def __init__(self, line):
        self.surface, temp = line.rstrip().split('\t')
        info = temp.split(',')
        self.pos = info[0]
        self.pos1 = info[1]
        self.base = info[6]
   
    @classmethod
    def load_cabocha(cls, fi):
        """Generieren Sie eine Morph-Instanz aus einer Datei im Cabocha-Gitterformat"""
        for is_eos, sentence in groupby(fi, key=lambda x: x == 'EOS\n'):
            if not is_eos:
                yield [cls(line) for line in sentence if not line.startswith('* ')]
    
    def __str__(self):
        return self.surface
    
    def __repr__(self):
        return 'q40.Morph({})'.format(', '.join((self.surface, self.pos, self.pos1, self.base)))
    

def main():
    sent_idx = arg_int()
    for i, sent_lis in enumerate(Morph.load_cabocha(sys.stdin), start=1):
        if i == sent_idx:
            # print(*sent_lis)
            print(repr(sent_lis))
            break

def arg_int():
    parser = argparse.ArgumentParser()
    parser.add_argument('-n', '--number', default='1', type=int)
    args = parser.parse_args()
    return args.number


if __name__ == '__main__':
    main()
!python q40.py -n2 < neko.txt.cabocha

[q40.Morph (, Symbol, leer,), q40.Morph (I, Nomenklatur, Synonym, I), q40.Morph (ha, Assistent, Zugehörigkeit, ha), q40.Morph (Katze, Substantiv, allgemein, Katze) ), q40.Morph (in, Hilfsverb, *, da), q40.Morph (ist, Hilfsverb, *, ist), q40.Morph (., Symbol, Interpunktion ,.)

Das Übergeben des Instanzvariablennamens an eine spezielle Klassenvariable namens "slots" spart Speicher und beschleunigt die Attributsuche. Stattdessen können Sie keine neuen Instanzvariablen von außerhalb hinzufügen, oder Sie können die Liste der Instanzvariablen nicht mit vars () abrufen.

Das Design ist so, dass es nicht instanziiert werden kann, ohne eine Reihe morphologischer Informationen zu übergeben. Magst du diesen Bereich?

Wenn Sie die Erklärung am Anfang der Funktionsdefinition usw. in das Zeichenfolgenliteral schreiben, wird sie als * docstring * behandelt. String-Literale können mit 3 Anführungszeichen gebrochen werden. Auf die Dokumentzeichenfolge kann in der Funktion "help ()", in Jupyter und im Editor verwiesen werden. Auch doctest und pydoc Es kann zusammen mit dem Modul verwendet werden.

Sie können elegant schreiben, indem Sie "groupby ()" für Dateien verwenden, deren Satzenden durch "EOS" ausgedrückt werden. Andererseits gibt es im Problem dieses Kapitels einen Teil wie "EOS \ nEOS" wegen Leerzeilen. Achten Sie also darauf, dass die Art und Weise, wie Sätze im Problemsatz gezählt werden, und die Art und Weise, wie Sätze nach "Gruppe nach ()" gezählt werden, unterschiedlich sind. ..

41. Lesen Sie das Ergebnis der Abhängigkeitsanalyse (Phrase / Abhängigkeit) und ein Beispiel für die Beantwortung der folgenden Fragen

Implementieren Sie zusätzlich zu> 40 die Klauselklasse Chunk. Diese Klasse enthält eine Liste von Morph-Elementen (Morph-Objekten) (Morphs), eine Liste verwandter Klauselindexnummern (dst) und eine Liste verwandter ursprünglicher Klauselindexnummern (srcs) als Mitgliedsvariablen. Lesen Sie außerdem das Analyseergebnis von CaboCha des Eingabetextes, drücken Sie einen Satz als Liste von Chunk-Objekten aus und zeigen Sie die Zeichenfolge und den Kontakt der Phrase des achten Satzes an. Verwenden Sie für die restlichen Probleme in Kapitel 5 das hier erstellte Programm.

Basierend auf dem objektorientierten Geist ist es besser, eine Satzklasse zu erstellen, da sie eine Beziehung von Satz-> Klausel-> Morphologie hat. Selbst wenn Sie es erstellen, ist die Instanzvariable nur "self.chunks" und nicht unbedingt erforderlich, da sie sich aus Sicht der Variablenverwaltung nicht wesentlich ändert.

Da jedoch "srcs" nicht gesetzt werden kann, bis das Analyseergebnis eines Satzes bis zum Ende gelesen ist, erscheint es naheliegend, dies beim Initialisieren des "Satz" -Objekts und bei der Verarbeitung auf Satzebene (Lesen der Cabocha-Datei, n-ter Satz) zuzulassen. Ich denke, es ist ein Vorteil, das Analyseergebnis von, das Empfangen von n als Befehlszeilenargument) und die Verarbeitung auf Anweisungsebene (nachfolgende Probleme, insbesondere die in 43 und höher gestellten) zu trennen.

Das Folgende ist ein Beispiel für die Antwort, enthält jedoch die Antworten auf die folgenden Fragen. Da es in * docstring * beschrieben ist, überspringen Sie den Code, der nicht mit Nr. 41 zusammenhängt, und verweisen Sie später darauf.

q41.py


from collections import defaultdict
from itertools import groupby, combinations
import sys

from q40 import Morph, arg_int


class Chunk:
    """Lesen Sie die Klauseln aus der Datei im Cabocha-Gitterformat"""
    __slots__ = ('idx', 'dst', 'morphs', 'srcs')
    
    # * 0 2D 0/0 -0.764522
    def __init__(self, line):
        info = line.rstrip().split()
        self.idx = int(info[1])
        self.dst = int(info[2].rstrip("D"))
        self.morphs = []
        self.srcs = []
    
    def __str__(self):
        return ''.join([morph.surface for morph in self.morphs])
    
    def __repr__(self):
        return 'q41.Chunk({}, {})'.format(self.idx, self.dst)
    
    def srcs_append(self, src_idx):
        """Fügen Sie den ursprünglichen Klauselindex hinzu. Satz.__init__()Benutzt in."""
        self.srcs.append(src_idx)
    
    def morphs_append(self, line):
        """Morphologie hinzufügen. Satz.__init__()Benutzt in."""
        self.morphs.append(Morph(line))
    
    def tostr(self):
        """Gibt die Oberflächenform der Klausel mit entfernten Symbolen zurück. Wird in q42 oder höher verwendet."""
        return ''.join([morph.surface for morph in self.morphs if morph.pos != 'Symbol'])
    
    def contain_pos(self, pos):
        """Gibt zurück, ob ein Teil der Phrase vorhanden ist. Wird in Q43 oder höher verwendet."""
        return pos in (morph.pos for morph in self.morphs)

    def replace_np(self, symbol):
        """Ersetzen Sie die Nomenklatur in der Phrase durch ein Symbol. Für q49."""
        morph_lis = []
        for pos, morphs in groupby(self.morphs, key=lambda x: x.pos):
            if pos == 'Substantiv':
                for morph in morphs:
                    morph_lis.append(symbol)
                    break
            elif pos != 'Symbol':
                for morph in morphs:
                    morph_lis.append(morph.surface)
        return ''.join(morph_lis)
        
    
class Sentence:
    """Lesen Sie eine Anweisung aus der Cabocha-Gitterformatdatei."""
    __slots__ = ('chunks', 'idx')
    
    def __init__(self, sent_lines):
        self.chunks = []
        
        for line in sent_lines:                    
            if line.startswith('* '):
                self.chunks.append(Chunk(line))
            else:
                self.chunks[-1].morphs_append(line)

        for chunk in self.chunks:
            if chunk.dst != -1:
                self.chunks[chunk.dst].srcs_append(chunk.idx)
    
    def __str__(self):
        return ' '.join([morph.surface for chunk in self.chunks for morph in chunk.morphs])
    
    @classmethod
    def load_cabocha(cls, fi):
        """Generieren Sie eine Satzinstanz aus einer Datei im Cabocha-Gitterformat"""
        for is_eos, sentence in groupby(fi, key=lambda x: x == 'EOS\n'):
            if not is_eos:
                yield cls(sentence)
    
    def print_dep_idx(self):
        """q41.Zeigen Sie den ursprünglichen Klauselindex und den Zielklauselindex an"""
        for chunk in self.chunks:
            print('{}:{} => {}'.format(chunk.idx, chunk, chunk.dst))
    
    def print_dep(self):
        """q42.Zeigen Sie die Oberflächenebene der ursprünglichen Klausel und der zugehörigen Klausel durch Tabulatoren getrennt an"""
        for chunk in self.chunks:
            if chunk.dst != -1:
                print('{}\t{}'.format(chunk.tostr(), self.chunks[chunk.dst].tostr()))

    def print_noun_verb_dep(self):
        """q43.Extrahieren Sie Klauseln mit Nomenklaturen in Bezug auf Klauseln mit Verben"""
        for chunk in self.chunks:
            if chunk.contain_pos('Substantiv') and self.chunks[chunk.dst].contain_pos('Verb'):
                print('{}\t{}'.format(chunk.tostr(), self.chunks[chunk.dst].tostr()))
                
    def dep_edge(self):
        """Damit pydot eine Abhängigkeit mit q44 ausgibt"""
        return [(f"{i}: {chunk.tostr()}", f"{chunk.dst}: {self.chunks[chunk.dst].tostr()}")
                    for i, chunk in enumerate(self.chunks) if chunk.dst != -1]
    
    def case_pattern(self):
        """q45.Extraktion von Verbfallmustern"""
        for chunk in self.chunks:
            for morph in chunk.morphs:
                if morph.pos == 'Verb':
                    verb = morph.base
                    particles = [] #Liste der Hilfswörter
                    for src in chunk.srcs:
                        #Fügen Sie das am weitesten rechts stehende Hilfswort in das Segment ein
                        particles.extend([word.base for word in self.chunks[src].morphs 
                                             if word.pos == 'Partikel'][-1:])
                    particles.sort()
                    print('{}\t{}'.format(verb, ' '.join(particles)))
                    #Ich benutze nur das Verb ganz links, damit ich schnell raus kann
                    break

    def pred_case_arg(self):
        """q46.Extraktion von Verb-Case-Frame-Informationen"""
        for chunk in self.chunks:
            for morph in chunk.morphs:
                if morph.pos == 'Verb':
                    verb = morph.base
                    particle_chunks = []
                    for src in chunk.srcs:
                        # (Partikel,Oberflächenschicht des ursprünglichen Segments)
                        particle_chunks.extend([(word.base, self.chunks[src].tostr()) 
                                                for word in self.chunks[src].morphs if word.pos == 'Partikel'][-1:])
                    if particle_chunks:
                        particle_chunks.sort()
                        particles, chunks = zip(*particle_chunks)
                    else:
                        particles, chunks = [], []

                    print('{}\t{}\t{}'.format(verb, ' '.join(particles), ' '.join(chunks)))
                    break
                    
    def sahen_case_arg(self):
        """q47.Functional Verb Syntax Mining"""
        #Flagge zum Extrahieren von Sa-Hen-Nase + Verb
        sahen_flag = 0
        for chunk in self.chunks:
            for morph in chunk.morphs:
                if sahen_flag == 0 and morph.pos1 == 'Verbindung ändern':
                    sahen_flag = 1
                    sahen = morph.surface
                elif sahen_flag == 1 and morph.base == 'Zu' and morph.pos == 'Partikel':
                    sahen_flag = 2
                elif sahen_flag == 2 and morph.pos == 'Verb':
                    sahen_wo = sahen + 'Zu'
                    verb = morph.base
                    particle_chunks = []
                    for src in chunk.srcs:
                        # (Partikel,Oberflächenschicht des ursprünglichen Segments)
                        particle_chunks.extend([(word.base, self.chunks[src].tostr()) for word in self.chunks[src].morphs 
                                         if word.pos == 'Partikel'][-1:])
                    for j, part_chunk in enumerate(particle_chunks[:]):
                        if sahen_wo in part_chunk[1]:
                            del particle_chunks[j]

                    if particle_chunks:
                        particle_chunks.sort()
                        particles, chunks = zip(*particle_chunks)
                    else:
                        particles, chunks = [], []

                    print('{}\t{}\t{}'.format(sahen_wo + verb, ' '.join(particles), ' '.join(chunks)))
                    sahen_flag = 0
                    break
                else:
                    sahen_flag = 0 

    def trace_dep_path(self):
        """q48.Verfolgen Sie Abhängigkeitspfade von Klauseln mit Nomenklatur bis root"""
        path = []
        for chunk in self.chunks:
            if chunk.contain_pos('Substantiv'):
                path.append(chunk)
                d = chunk.dst
                while d != -1:
                    path.append(self.chunks[d])
                    d = self.chunks[d].dst
                
                yield path
                path = []

    def print_noun2noun_path(self):
        """q49.Extraktion von Abhängigkeitspfaden zwischen Nomenklaturen"""
        #Liste der Chunk-Liste mit dem Pfad von der Klausel mit der Nomenklatur zur Wurzel (1 Satz)
        all_paths = list(self.trace_dep_path())
        arrow = ' -> '
        #Liste der Klausel-IDs für jeden Pfad
        all_paths_set = [{chunk.idx for chunk in chunks} for chunks in all_paths]
        # all_Wählen Sie ein Paar aus Pfaden
        for p1, p2 in combinations(range(len(all_paths)), 2):
            #Finden Sie eine gemeinsame Phrase k
            intersec = all_paths_set[p1] & all_paths_set[p2]
            len_intersec = len(intersec)
            len_smaller = min(len(all_paths_set[p1]), len(all_paths_set[p2]))
            #Der gemeinsame Teil ist nicht leer und auch keine Teilmenge
            if 0 < len_intersec < len_smaller:
                #Pfad anzeigen
                k = min(intersec)
                path1_lis = []
                path1_lis.append(all_paths[p1][0].replace_np('X'))
                for chunk in all_paths[p1][1:]:
                    if chunk.idx < k:
                        path1_lis.append(chunk.tostr())
                    else:
                        break
                path2_lis = []
                rest_lis = []
                path2_lis.append(all_paths[p2][0].replace_np('Y'))                     
                for chunk in all_paths[p2][1:]:
                    if chunk.idx < k:
                        path2_lis.append(chunk.tostr())
                    else:
                        rest_lis.append(chunk.tostr())
                print(' | '.join([arrow.join(path1_lis), arrow.join(path2_lis),
                                 arrow.join(rest_lis)]))
        #Suchen und Anzeigen von Mustern im Zusammenhang mit der Nomenklatur aus der Nomenklatur
        for chunks in all_paths:
            for j in range(1, len(chunks)):
                if chunks[j].contain_pos('Substantiv'):
                    outstr = []
                    outstr.append(chunks[0].replace_np('X'))
                    outstr.extend(chunk.tostr() for chunk in chunks[1:j])
                    outstr.append(chunks[j].replace_np('Y'))
                    print(arrow.join(outstr))
                    
    
def main():
    sent_id = arg_int()
    for i, sent in enumerate(Sentence.load_cabocha(sys.stdin), start=1):
        if i == sent_id:
            sent.print_dep_idx()
            break


if __name__ == '__main__':
    main()

!python q41.py -n8 < neko.txt.cabocha

0: Dies => 1 1: Ein Schüler ist => 7 2: Manchmal => 4 3: Wir => 4 4: Fang => 5 5: Kochen => 6 6: Essen => 7 7: Es ist eine Geschichte. => -1

Da das Ziel dieses Kapitels darin besteht, die Klassendefinition zu lernen, werde ich die Erklärung der folgenden Probleme überspringen.

Ergänzung zu speziellen Methoden

Es ist eine etwas fortgeschrittene Geschichte, also können Sie sie überspringen.

Was im Code der Klasse "Satz" besorgniserregend ist, ist, dass Beschreibungen wie "für Chunk in self.chunks" und "self.chunks [i]" häufig vorkommen. Wenn Sie die folgende spezielle Methode definieren, können Sie "for chunk in self" oder "self [i]" verwenden.

    def __iter__(self):
        return iter(self.chunks)

    def __getitem__(self, key):
        return self.chunks[key]

Wenn die Liste mit der for-Anweisung gedreht wurde, wurde sie von der Funktion iter () intern intern in einen Iterator konvertiert. Und die Funktion iter () ruft die Methode __iter () __` des Objekts auf. Wenn Sie es also so schreiben, können Sie die Satzinstanz selbst mit einer for-Anweisung drehen.

Der Indexzugriff ist durch Definieren von getitem () möglich.

Wenn die Klasse "Chunk" auf dieselbe Weise definiert ist, kann die folgende for-Anweisung an die Instanz gesendet werden.

for chunk in sentence:
    for morph in chunk:

Vielleicht ist auch der Python-Wrapper für den Abhängigkeitsanalysator in der Welt so gemacht. Ich denke auch, dass es natürlicher ist, die Methoden für q41 und 42 in der Klasse "Chunk" zu definieren, die obige "for" -Anweisung außen zu schreiben und sie darin aufzurufen.

42 und 43 werden weggelassen, weil es nichts Besonderes zu sagen gibt.

44. Visualisierung abhängiger Bäume

Visualisieren Sie den Abhängigkeitsbaum eines bestimmten Satzes als gerichteten Graphen. Zur Visualisierung ist es ratsam, den Abhängigkeitsbaum in die DOT-Sprache zu konvertieren und Graphviz zu verwenden. Verwenden Sie pydot, um gerichtete Diagramme direkt aus Python zu visualisieren.

Ich werde mein Bestes tun, um Graphviz und pydot zu installieren. Viele Leute sagen, dass Pydot-ng verwendet werden sollte, da Pydot nicht lange gewartet wurde, aber in meiner Umgebung hat es mit Pydot funktioniert, also verwende ich Pydot. Ich habe nur Pydot mit 100 Schlägen verwendet, daher werde ich es nicht besonders erklären. Zur Implementierung habe ich auf [diesen Blog] verwiesen (https://plaza.rakuten.co.jp/kugutsushi/diary/200711070000/).

q44.py


import sys

from q40 import arg_int
from q41 import Sentence
import pydot


def main():
    sent_id = arg_int()
    for i, sent in enumerate(Sentence.load_cabocha(sys.stdin), start=1):
        if i == sent_id:
            edges = sent.dep_edge()
            n = pydot.Node('node')
            n.fontname="MS P Gothic"
            n.fontsize = 9
            graph = pydot.graph_from_edges(edges, directed=True)
            graph.add_node(n)
            graph.write_jpeg(f"dep_tree_neko{i}.jpg ")
            break

if __name__ == "__main__":
    main()

!python q44.py  -n8 < neko.txt.cabocha
from IPython.display import Image
Image("dep_tree_neko8.jpg ")

output_23_0.jpg

Die Richtung des Abhängigkeitspfeils kann auf diese Weise umgekehrt werden (insbesondere wenn sie auf universellen Abhängigkeiten basiert), aber ich denke, dass beide für dieses Problem in Ordnung sind.

(Aufgrund der Angabe von "graph_from_edges ()" werden mehrere Klauseln mit derselben Oberflächenschicht (und unterschiedlichen IDs) in der Anweisung als derselbe Knoten betrachtet. Um dies zu vermeiden, wird "graph_from_edges ()" verwendet Es ist notwendig, die Implementierung des Oberflächenschichtsystems zu ändern oder eine ID hinzuzufügen.)

(Die Schriftart ist MS P Gothic, da meine Umgebung Ubuntu mit WSL ist und ich die japanische Schriftart nicht finden konnte. Daher habe ich die Technik der Referenzierung auf die Windows-Schriftart verwendet.)

45. Extraktion von Verbfallmustern

Ich möchte den diesmal verwendeten Satz als Korpus betrachten und die möglichen Fälle japanischer Prädikate untersuchen. Stellen Sie sich das Verb als Prädikat und das Hilfsverb der Phrase, die sich auf das Verb bezieht, als Fall vor und geben Sie das Prädikat und den Fall in einem durch Tabulatoren getrennten Format aus. Stellen Sie jedoch sicher, dass die Ausgabe den folgenden Spezifikationen entspricht.

  • In einer Klausel, die ein Verb enthält, wird die Grundform des Verbs ganz links als Prädikat verwendet.
  • Der Fall ist das Hilfswort, das sich auf das Prädikat bezieht
  • Wenn es mehrere Hilfswörter (Phrasen) gibt, die sich auf das Prädikat beziehen, ordnen Sie alle Hilfswörter in Wörterbuchreihenfolge an, die durch Leerzeichen getrennt sind.

Betrachten Sie den Beispielsatz (8. Satz von neko.txt.cabocha), dass "ich hier zum ersten Mal einen Menschen gesehen habe". Dieser Satz enthält zwei Verben, "begin" und "see", und die Phrase, die sich auf "begin" bezieht, wird als "here" analysiert, und die Phrase, die sich auf "see" bezieht, wird als "I am" und "thing" analysiert. Sollte die folgende Ausgabe erzeugen.

Siehe `` `

Speichern Sie die Ausgabe dieses Programms in einer Datei und überprüfen Sie die folgenden Elemente mit UNIX-Befehlen.

- Kombination von Prädikaten und Fallmustern, die häufig im Korpus vorkommen
- Das Fallmuster der Verben "do", "see" und "give" (in der Reihenfolge der Häufigkeit des Auftretens im Korpus anordnen)

Wenn Sie den Fall untersuchen möchten, sollten Sie sich auf den Fallassistenten konzentrieren, aber der weinenden Problemstellung folgen. Ordnen Sie die Hilfswörter in lexikalischer Reihenfolge an. In der Problemstellung steht nicht, ob der Fall ausgegeben werden soll, in dem der Sachbearbeiter des Prädikats kein Hilfswort enthält, aber da der Fall weggelassen wird, habe ich beschlossen, ihn zu aggregieren.

Pythons sorted () undlist.sorted ()können auch Zeichenfolgen sortieren, sie befinden sich jedoch in der Wörterbuchreihenfolge basierend auf Codepunkten und unterscheiden sich von der in japanischen Wörterbüchern verwendeten alphabetischen Reihenfolge. Lass es uns wissen.

>>> sorted(['Kagaku', 'Kakashi']) 

['Kakashi', 'Kakaku']

Die obige Beispiellösung verwendet "verlängern ()". Eine Methode, die Listen mit demselben Ergebnis wie "+" verkettet. Achten Sie darauf, list.append nicht mit list.extend zu verwechseln.

!python q45.py < neko.txt.cabocha | head

Geboren werden Tsukuka Machen Durch Weinen Machen Am Anfang Sehen Hör mal zu Fangen Kochen

!python q45.py < neko.txt.cabocha | sort | uniq -c | sort -rn | head -20

704 452 435 Ich denke 202 199 werden 188 175 Schau 159 122 sagen 117 113 108 98 siehe 97 Wenn Sie sehen 94 90 89 85 80 sehen

!python q45.py < neko.txt.cabocha | grep -E "^(Machen|sehen|geben)\s" | sort | uniq -c | sort -nr | head -20

452 435 188 175 Schau 159 117 113 98 siehe 90 85 80 sehen 61 60 60 51 51 von 46 40 39 Was ist 37

Wahrheitswerturteil

Da die Beschreibung wie "if list:" im Antwortbeispiel von Nr. 46 verwendet wird, werde ich das Wahrheitswerturteil erläutern. Jedes andere Objekt als bedingte Ausdrücke kann in die if-Anweisung geschrieben werden, und was passiert in diesem Fall mit dem Wahrheitswerturteil? Dokumentation (/stdtypes.html#truth), beziehen Sie sich also bitte darauf. Kurz gesagt, "Keine", nullähnliche Dinge und "x" mit "len (x) == 0" werden alle als falsch behandelt, und die anderen werden als wahr behandelt. Wenn Sie dies wissen, können Sie leicht Dinge wie "Wenn die Liste nicht leer ist" schreiben. Wenn Sie sich nicht sicher sind, wenden Sie die integrierte Funktion bool () auf verschiedene Objekte an.

Es gibt nichts anderes zu sagen, also wird 46 weggelassen.

47. Mining der funktionalen Verbsyntax

Ich möchte nur auf den Fall achten, in dem das Verb wo case eine Sa-hen-Verbindungsnomenklatur enthält. Ändern Sie 46 Programme, um die folgenden Spezifikationen zu erfüllen.

-Nur wenn die Phrase bestehend aus "Sahen-Verbindungsnomen + (Hilfsverb)" mit dem Verb verwandt ist -Das Prädikat lautet "Sahen-Verbindungsnomen + ist die Grundform von + Verb". Wenn die Phrase mehrere Verben enthält, verwenden Sie das Verb ganz links. -Wenn das Prädikat mehrere Hilfswörter (Phrasen) enthält, ordnen Sie alle Hilfswörter in der durch Leerzeichen getrennten Wörterbuchreihenfolge an. -Wenn es mehrere Klauseln gibt, die sich auf das Prädikat beziehen, ordnen Sie alle durch Leerzeichen getrennten Begriffe an (richten Sie sie nach der Reihenfolge der Hilfswörter aus).

Zum Beispiel sollte die folgende Ausgabe aus dem Satz "Der Meister wird auf den Brief antworten, auch wenn er an einen anderen Ort kommt" erhalten werden.

`Wenn Sie antworten, ist der Master`

Speichern Sie die Ausgabe dieses Programms in einer Datei und überprüfen Sie die folgenden Elemente mit UNIX-Befehlen. -Predikate, die häufig im Korpus vorkommen (Sahen-Verbindungsnomenklatur + + Verb) -Prädiktives und Hilfsmuster, das häufig im Korpus auftritt

Die Sa-Hen-Verbindungsnomenklatur ist eine Nomenklatur, die durch Hinzufügen von "zu" am Ende in eine Sa-Variable umgewandelt werden kann, z. B. "Antwort". Übrigens ist in der Schulgrammatik "Antwort" ein Wort, aber in der morphologischen Analyse wird es normalerweise wie "Antwort / Antwort" unterteilt.

Bitte verzeihen Sie mir außerdem, dass das Antwortbeispiel ein schrecklicher Push-Code ist ...

!python q47.py < neko.txt.cabocha | cut -f1 | sort | uniq -c | sort -nr | head

30 Antwort 21 Sag Hallo 14 imitieren 13 reden 13 Streit 6 Machen Sie ein Nickerchen 5 Übung 5 Stellen Sie eine Frage 5 Stellen Sie eine Frage 5 Hören Sie sich die Geschichte an

!python q47.py < neko.txt.cabocha | cut -f 1,2 | sort | uniq -c | sort -nr | head

8 imitieren 6 Wenn Sie antworten 6 Streit 4 Übung 4 Um zu antworten 4 Antworten 4 Hören Sie sich die Geschichte an 4 Wenn Sie Hallo sagen 4 Ich werde Hallo sagen 3 Eine Frage stellen

48. Extrahieren von Pfaden von der Nomenklatur zu den Wurzeln

Extrahieren Sie für eine Klausel, die die gesamte Nomenklatur des Satzes enthält, den Pfad von dieser Klausel zum Stamm des Syntaxbaums. Der Pfad im Syntaxbaum muss jedoch die folgenden Spezifikationen erfüllen. -Jede Klausel wird durch eine (oberflächliche) morphologische Sequenz dargestellt

  • Verketten Sie die Ausdrücke jeder Klausel mit `" -> "` von der Startklausel bis zur Endklausel des Pfades.

Aus dem Satz "Ich habe hier zum ersten Mal einen Menschen gesehen" (8. Satz von neko.txt.cabocha) sollte die folgende Ausgabe erhalten werden.

ich bin->sah
Hier->Beginnen mit->Mensch->Dinge->sah
Mensch->Dinge->sah
Dinge->sah

Alles was Sie tun müssen, ist dem Ziel mit while oder rekursiv zu folgen. In Anbetracht des folgenden Problems habe ich eine Methode nur für die Verarbeitung bis zu einem Schritt vor der Ausgabe erstellt.

q48.py


import sys

from q40 import arg_int
from q41 import Sentence


def main():
    sent_id = arg_int()
    for i, sent in enumerate(Sentence.load_cabocha(sys.stdin), start=1):
        if i == sent_id:
            for chunks in sent.trace_dep_path():
                print(' -> '.join([chunk.tostr() for chunk in chunks]))
            break

if __name__ == '__main__':
    main()
!python q48.py -n6 < neko.txt.cabocha

Ich sah-> Hier-> zum ersten Mal-> Mensch-> habe ich etwas gesehen-> Ich habe ein menschliches Ding gesehen Ich habe etwas gesehen->

(Hinzugefügt am 18.05.2018) Nachdem ich Kommentare erhalten hatte, stellte ich fest, dass das Beispiel der Abhängigkeitsanalyse der Problemstellung ein Analysefehler war. Es ist geplant, die in [issue] verwendeten Beispielsätze (https://github.com/nlp100/nlp100.github.io/issues/19) zu ändern. Der Hauptzweck von Kapitel 5 von 100 Schlägen besteht darin, die Klassendefinition zu üben, und ich denke, dass der Grund, warum CaboCha angegeben wird, einfach darin besteht, dass es einfach zu verwenden ist. Andererseits wird auch erwogen, eine Problemstellung zu entwickeln, um dem Abhängigkeitsanalysator einen größeren Bereich zu geben. GiNZA ist heutzutage auch einfach zu bedienen.

49. Extraktion von Abhängigkeitspfaden zwischen Nomenklatur

Extrahieren Sie den kürzesten Abhängigkeitspfad, der alle Nomenklaturpaare im Satz verbindet. Die Klauselnummer des Nomenklaturpaars ist jedoch i Wenn> und j (i <j), muss der Abhängigkeitspfad die folgenden Spezifikationen erfüllen.

  • Ähnlich wie bei Problem 48 wird der Pfad durch Verketten der Ausdrücke (oberflächenmorphologischen Elemente) jeder Phrase von der Startklausel bis zur Endklausel mit "->" ausgedrückt. -Ersetzen Sie die in den Abschnitten i und j enthaltene Nomenklatur durch X bzw. Y.

Darüber hinaus kann die Form des Abhängigkeitspfads auf zwei Arten betrachtet werden.

-Wenn sich Klausel j auf dem Pfad von Klausel i zum Stammverzeichnis des Syntaxbaums befindet: Zeigen Sie den Pfad von Klausel i zu Klausel j an

  • Anders als oben, wenn sich Klausel i und Klausel j an einer gemeinsamen Klausel k auf dem Pfad von Klausel j zur Wurzel des Syntaxbaums überschneiden: der Pfad unmittelbar vor Klausel i zu Klausel k und der Pfad von Klausel j zu kurz vor Klausel k> Zeigen Sie den Inhalt von Klausel k an, indem Sie ihn mit "|" verketten.

Aus dem Satz "Ich habe hier zum ersten Mal einen Menschen gesehen" (8. Satz von neko.txt.cabocha) sollte beispielsweise die folgende Ausgabe erhalten werden.

X ist|In Y.->Beginnen mit->Mensch->Dinge|sah
X ist|Genannt Y.->Dinge|sah
X ist|Y.|sah
In X.->Beginnen mit-> Y
In X.->Beginnen mit->Mensch-> Y
X genannt-> Y

Dies ist übrigens das schwierigste Problem in der 2020-Version von 100 Schlägen. Erstens ist die Problemstellung sehr schwer zu verstehen. Und selbst wenn Sie die Bedeutung verstehen, ist es immer noch schwierig. Lassen Sie uns vorerst nur die Bedeutung der Problemstellung verstehen.

Kurz gesagt, das Problem besteht darin, die Ausgabe von Problem 48 in so etwas umzuwandeln.

Unterhalb des Ausgabebeispiels befinden sich jedoch drei Traps, die "Wenn Klausel j auf dem Pfad von Klausel i zum Stamm des Syntaxbaums vorhanden ist: Anzeigen des Pfads von Klausel i zu Klausel j" entsprechen. Glücklicherweise scheint dieser Teil einfach zu implementieren zu sein. Da der in Frage 48 stehende Pfad ein Nomenklaturstart ist, ist der erste Satz aller vier Pfade ein Kandidat für i. Alles was Sie tun müssen, ist von dort aus nach j zu suchen. Und schließlich ersetzen Sie die Nomenklatur von i und j durch X und Y (ich denke, dass "X-> Y" anstelle von "X-> Y" ist, aber es ist Gogogogogogogo).

Der Teil, der schwierig zu sein scheint, ist "anders als oben". Das bin ich->"Ich habe gesehen" und "Mensch"->Dinge->Wenn es einen Pfad wie "Ich habe ihn gesehen" gibt, kombinieren Sie ihn und sagen Sie "Ich bin es"|Mensch->Dinge|Als "ich sah", weitere Klausel i,Ersetzen Sie die Nomenklatur von j durch "X ist|Genannt Y.->Dinge|"Ich sah es."

Da es in Problem 48 vier Pfade gibt, müssen Sie zwei Pfade auswählen und die Schleife "4C2" mal ausführen. Es ist schmerzhaft, wenn Sie "itertools.combinations ()" nicht kennen. Und wenn Sie die Paare in einer Teilmengenbeziehung nicht ignorieren, z. B. "Mensch-> Ich habe etwas gesehen->" und "Ich habe etwas gesehen->", wäre das schlecht. Dieser Vorgang ist ärgerlich. Auch für die Klauseln i und j ist die erste Klausel der beiden Pfade ein Kandidat, aber der Prozess des Suchens nach einer gemeinsamen Phrase k von dort und der Prozess des Erstellens einer Ausgabezeichenfolge, nachdem sie gefunden wurde, sind mühsam. ~~ Es ist weder eine Einführung in die Verarbeitung natürlicher Sprache noch eine Einführung in Python, daher denke ich nicht, dass es notwendig ist, dies zu erzwingen.

Zusammenfassung

--Klasse

abschließend

Damit ist die Einführungsserie zu Python mit 100 Klopfen Sprachverarbeitung abgeschlossen.

Recommended Posts

[Kapitel 5] Einführung in Python mit 100 Klopfen Sprachverarbeitung
[Kapitel 3] Einführung in Python mit 100 Klopfen Sprachverarbeitung
[Kapitel 2] Einführung in Python mit 100 Klopfen Sprachverarbeitung
[Kapitel 4] Einführung in Python mit 100 Klopfen Sprachverarbeitung
[Kapitel 6] Einführung in Scicit-Learn mit 100 Klopfen Sprachverarbeitung
100 Sprachverarbeitungsklopfen mit Python (Kapitel 1)
100 Sprachverarbeitungsklopfen mit Python (Kapitel 3)
100 Sprachverarbeitungsklopfen mit Python (Kapitel 2, Teil 2)
100 Sprachverarbeitungsklopfen mit Python (Kapitel 2, Teil 1)
100 Sprachverarbeitungsklopfen ~ Kapitel 1
Einführung in die Python-Sprache
100 Sprachverarbeitung klopft Kapitel 2 (10 ~ 19)
Erste Schritte mit Python mit 100 Klopfen bei der Sprachverarbeitung
100 Sprachverarbeitungsklopfen mit Python 2015
100 Sprachverarbeitung Knock Kapitel 1 (Python)
100 Sprachverarbeitung Knock Kapitel 2 (Python)
Rehabilitation von Python- und NLP-Kenntnissen ab "100 Language Processing Knock 2015" (Kapitel 1)
[Sprachverarbeitung 100 Schläge 2020] Zusammenfassung der Antwortbeispiele von Python
Einführung in die verteilte Parallelverarbeitung von Python durch Ray
Ich habe versucht, die 2020-Version von 100 Sprachverarbeitungsproblemen zu lösen [Kapitel 3: Reguläre Ausdrücke 20 bis 24]
Ich habe versucht, die 2020-Version von 100 Sprachverarbeitungsproblemen zu lösen [Kapitel 1: Vorbereitungsbewegung 00-04]
Ich habe versucht, die 2020-Version von 100 Sprachverarbeitungsproblemen zu lösen [Kapitel 1: Vorbereitungsbewegung 05-09]
[Sprachverarbeitung 100 Schläge 2020] Kapitel 3: Reguläre Ausdrücke
100 Klicks in der Verarbeitung natürlicher Sprache Kapitel 4 Kommentar
[Sprachverarbeitung 100 Schläge 2020] Kapitel 6: Maschinelles Lernen
100 Sprachverarbeitung Knock Kapitel 1 in Python
100 Sprachverarbeitungsklopfen 2020: Kapitel 4 (morphologische Analyse)
[Sprachverarbeitung 100 Schläge 2020] Kapitel 5: Abhängigkeitsanalyse
[Einführung in Python3 Tag 13] Kapitel 7 Zeichenfolgen (7.1-7.1.1.1)
[Einführung in Python3 Tag 14] Kapitel 7 Zeichenfolgen (7.1.1.1 bis 7.1.1.4)
Einführung in Protobuf-c (C-Sprache ⇔ Python)
[Sprachverarbeitung 100 Schläge 2020] Kapitel 1: Vorbereitende Bewegung
Die Bildverarbeitung mit Python 100 klopft an die Binärisierung Nr. 3
[Sprachverarbeitung 100 Schläge 2020] Kapitel 7: Wortvektor
[Einführung in Python3 Tag 15] Kapitel 7 Zeichenfolgen (7.1.2-7.1.2.2)
10 Funktionen von "Sprache mit Batterie" Python
100 Sprachverarbeitung klopfen 2020: Kapitel 3 (regulärer Ausdruck)
[Sprachverarbeitung 100 Schläge 2020] Kapitel 8: Neuronales Netz
[Sprachverarbeitung 100 Schläge 2020] Kapitel 2: UNIX-Befehle
[Sprachverarbeitung 100 Schläge 2020] Kapitel 9: RNN, CNN
100 Bildverarbeitung mit Python Knock # 2 Graustufen
100 Sprachverarbeitung Knock Kapitel 1 von Python
[Sprachverarbeitung 100 Schläge 2020] Kapitel 4: Morphologische Analyse
[Einführung in Python3 Tag 21] Kapitel 10 System (10.1 bis 10.5)
[Raspi4; Einführung in den Sound] Stabile Aufzeichnung der Toneingabe mit Python ♪
Rehabilitation von Python- und NLP-Kenntnissen ab "Knock 100 Language Processing 2015" (Kapitel 2, zweite Hälfte)
Rehabilitation von Python- und NLP-Kenntnissen ab "100 Language Processing Knock 2015" (Kapitel 2, erste Hälfte)
100 Sprachverarbeitungsklopfen 03 ~ 05
100 Sprachverarbeitungsklopfen (2020): 40
Grundlagen der binärisierten Bildverarbeitung durch Python
100 Sprachverarbeitungsklopfen (2020): 32
Zusammenfassung von Kapitel 2 der Einführung in Entwurfsmuster, die in Java gelernt wurden
IPynb-Bewertungssystem mit TA von Introduction to Programming (Python)
Ich habe mit GiNZA v3.1 Kapitel 4 100 Sprachverarbeitungsklopfen 2020 durchgeführt
100 Sprachverarbeitungsklopfen (2020): 35
100 Sprachverarbeitungsklopfen (2020): 47
100 Sprachverarbeitungsklopfen (2020): 39
Kapitel 4 Zusammenfassung der Einführung in Entwurfsmuster, die in Java gelernt wurden
100 Sprachverarbeitungsklopfen (2020): 22