[Chapitre 5] Introduction à Python avec 100 coups de traitement du langage

Cet article est une suite de mon livre Introduction à Python avec 100 Knock. Je vais principalement expliquer la classe en utilisant 100 knocks Chapter 5.

Tout d'abord, installons CaboCha et analysons le "Je suis un chat" d'Aozora Bunko.

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

Une nomenclature, un nombre, *, *, *, *, un, un, un EOS EOS * 0 2D 0/0 -0.764522 Symbole, vide, *, *, *, *, ,, * 1 2D 0/1 -0.764522 I nomenclature, synonymes, général, *, *, *, I, Wagahai, Wagahai Est un assistant, un assistant, *, *, *, *, est, ha, wa * 2 -1D 0/2 0.000000 Nomenclature de chat, général, *, *, *, *, chat, chat, chat Verbe auxiliaire, *, *, *, spécial da, forme continue, da, de Verbe auxiliaire, *, *, *, 5e Dan / La ligne Al, forme de base, aru, al, al .. Symboles, signes de ponctuation, *, *, *, *,. ,. ,. EOS

Une ligne comme «* numéro de clause, numéro de clause de destination D» a été ajoutée.

classe

En premier lieu, qu'est-ce que la programmation orientée objet? Si vous allez au point de ... cela mènera à une controverse, je vais donc me concentrer sur la classe Python. Je pense que ce sera plus facile si vous connaissez Java.

Pourquoi utiliser des classes

En ce qui concerne les problèmes de ce chapitre, la phrase->Phrase->Si vous essayez de gérer la structure hiérarchique de la morphologie sans utiliser de classes, (instruction)|Phrase|(Variable) pour la morphologie|Fonctions) se mélangent et cela devient un gros problème.forLa phrase est susceptible de prêter à confusion. Vous pouvez utiliser des classes pour regrouper les fonctions par type d'argument, et vous pouvez réduire la portée de la variable, ce qui facilite un peu le codage.

Explication de divers termes

Regardons un exemple du type de données qui représente une date, datetime.date.

import datetime
#Instanciation
a_day = datetime.date(2022,2,22)
print(a_day)
print(type(a_day))
#Variable d'instance
print(a_day.year, a_day.month, a_day.day)
#Variable de classe
print(datetime.date.max, a_day.max)
#Méthode d'instance
print(a_day.weekday(), datetime.date.weekday(a_day))
#Méthode de classe
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 () crée une entité (instance) de type datetime.date et définit (initialise) la valeur en fonction de la date passée à l'argument.

L'instance ʻa_day` a des valeurs (attributs) spécifiques à l'instance qui représentent l'année, le mois et le jour, et on les appelle des variables d'instance. D'autre part, une valeur commune à toutes les instances de type «datetime.date» est appelée une variable de classe. Dans cet exemple, la date maximale possible est la variable de classe. Les fonctions qui nécessitent une instance sont appelées des méthodes d'instance et les fonctions qui ne le sont pas sont appelées des méthodes de classe.

Notez que ʻa_day.weekday () et datetime.date.weekday (a_day) `sont équivalents pour les méthodes d'instance. De plus, pensez à Python comme à la conversion du premier en le second et à son exécution. À propos, la valeur de retour signifie que 0 est lundi et 1 est mardi.

Définition de classe

Le dessin de conception du type de données est * class *. Définissons réellement la classe. Après tout, est-il facile de comprendre la classe qui imite un certain monstre?

class Nezumi:
    #Variable de classe
    types = ('Denki',)
    learnable_moves = {'Denkou Sekka', 'Kaminari', 'Denji'}
    base_hp = 35
    
    #Méthode d'initialisation
    def __init__(self, name, level):
        #Variable d'instance
        self.name = name
        self.level = level
        self.learned_moves = []
    
    #Méthode d'instance
    def learn(self, move):
        if move in self.learnable_moves:
            self.learned_moves.append((move))
            print(f'{self.name}Est nouveau{move}Je me suis souvenu!')
            
    #Méthode de classe
    @classmethod
    def hatch(cls):
        nezumi = cls('souris ou rat', 1)
        nezumi.learned_moves.append('Denkou Sekka')
        print('L'œuf est né à la place de la souris!')
        return nezumi

#Instanciation
reo = Nezumi('Leo', 44)
#Confirmation de la variable membre
print(reo.name, reo.level, reo.learned_moves, reo.types)
#Appel de méthode d'instance
reo.learn('Kaminari')

Lion 44 [] Denki Leo a appris un nouveau Kaminari!

#Appel de méthode de classe
nezu = Nezumi.hatch()
#Confirmation de variable d'instance
print(nezu.name, nezu.level, nezu.learned_moves)

L'œuf est né à la place de la souris! Souris 1 ['Denkou Sekka']

__init __ () est une méthode qui initialise les variables d'instance. Cela s'appelle une méthode d'initialisation ou un initialiseur. Le premier argument, «self», représente une instance. Et ce qui est défini dans la méthode sous la forme de self.variable name devient une variable d'instance.

En appelant la classe Nezumi définie comme Nezumi (), après instanciation (new) en interne, __init __ () ʻest exécuté pour l'instance créée self`.

L'instance «reo» est affectée au premier argument «self» de la méthode d'instance. Appeler comme reo.learn ('Kaminari') exécutera Nezumi.learn (reo, 'Kaminari'). C'est pourquoi nous avons besoin de ce «moi».

Les méthodes de classe sont définies avec un * décorateur * appelé @ classmethod. Dans les méthodes de classe, il est courant de décrire une méthode d'initialisation spéciale. L'objet de classe «Nezumi» est affecté au premier argument «cls». Par conséquent, cls ('mouse', 1) est identique à Nezumi ('mouse', 1).

Au fait, dans l'exemple ci-dessus, les variables d'instance sont consultées et vérifiées une par une, mais si vous utilisez la fonction intégrée vars (), la liste sera renvoyée au format dict.

Méthode spéciale

Une méthode qui est appelée sans autorisation lorsqu'une opération / opération spéciale est effectuée est appelée une méthode spéciale. __init __ () est l'un d'entre eux. Il existe [de nombreuses] méthodes spéciales (https://docs.python.org/ja/3/reference/datamodel.html#special-method-names), donc je n'entrerai pas trop dans les détails, mais voici les suivantes.

--__str__ () : Définit la chaîne de caractères d'affichage lorsqu'elle est passée àprint ()etc. --__repr__ () : définit la chaîne d'affichage pour le débogage. Vous pouvez le voir en le laissant évaluer en mode interactif ou en le passant à repr (). --__len__ () : définit la valeur de retour lorsqu'elle est transmise àlen ().

Membre privé

Python n'a pas cette fonctionnalité. Il est habituel de le préfixer avec un trait de soulignement, tel que «_spam».

Héritage

Description de l'héritage lui-même n'est pas si long et apparaît parfois dans le code d'apprentissage en profondeur. Cependant, ce n'est pas nécessaire dans ce chapitre, et il y a beaucoup d'histoires d'accompagnement (use, super (), namespace, static method, etc.), donc je vais l'omettre. ~~ J'ai apporté un exemple qui semble facile à expliquer l'héritage. ~~

Classe de données

Un module, dataclasses, a été ajouté pour définir facilement des classes pour la rétention de données dans Python 3.7. Il définit «init ()» et «repr ()» sans permission. Je pense qu'il peut également être utilisé dans ce problème, mais je vais l'omettre car je me souviens peut-être de certains types tels que les annotations de type.

40. Lecture du résultat de l'analyse des dépendances (morphologie)

Implémentez la classe Morph qui représente la morphologie. Cette classe a la forme de surface (surface), la forme de base ( base), les paroles de partie (pos) et la sous-classification des paroles de partie 1 ( pos1) comme variables membres. De plus, lisez le résultat de l'analyse de CaboCha (neko.txt.cabocha), exprimez chaque phrase sous la forme d'une liste d'objets Morph et affichez la chaîne d'éléments morphologiques de la troisième phrase.

Voici un exemple de réponse.

q40.py


import argparse
from itertools import groupby
import sys


class Morph:
    """Lire une ligne du fichier de format de treillis cabocha"""
    __slots__ = ('surface', 'pos', 'pos1', 'base')
    #Mon nom,Synonyme,Général,*,*,*,je,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):
        """Générer une instance Morph à partir d'un fichier au format de treillis cabocha"""
        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 (, symbole, vide,), q40.Morph (I, nomenclature, synonyme, I), q40.Morph (ha, assistant, affiliation, ha), q40.Morph (chat, nom, général, chat) ), q40.Morph (in, verbe auxiliaire, *, da), q40.Morph (is, verbe auxiliaire, *, est), q40.Morph (., Symbole, ponctuation,.)]

Passer le nom de la variable d'instance à une variable de classe spéciale appelée __slots__ économise de la mémoire et accélère la recherche d'attributs. Au lieu de cela, vous ne pourrez pas ajouter de nouvelles variables d'instance de l'extérieur, ou vous ne pourrez pas obtenir la liste des variables d'instance avec vars ().

Le design est tel qu'il ne peut pas être instancié sans passer une ligne d'informations morphologiques. Aimez-vous cette région?

Si vous écrivez l'explication dans la chaîne de caractères littérale au début de la définition de la fonction, etc., elle sera traitée comme * docstring *. Les chaînes littérales peuvent être brisées en utilisant 3 guillemets. La docstring peut être référencée sur la fonction help (), sur Jupyter et sur l'éditeur. Aussi, doctest et pydoc Il peut être utilisé avec le module.

Vous pouvez écrire élégamment en utilisant groupby () pour les fichiers dont la fin de phrase est exprimée par ʻEOS. D'un autre côté, dans le problème de ce chapitre, il y a une partie comme ʻEOS \ nEOS à cause des lignes vides, alors faites attention que la façon de compter les phrases dans la phrase problématique et la façon de compter les phrases par group by () soient différentes. ..

41. Lecture du résultat de l'analyse des dépendances (phrase / dépendance) et exemple de réponse aux questions suivantes

En plus de> 40, implémentez la classe de clause Chunk. Cette classe contient une liste d'éléments morph (objets Morph) (morphs), une liste de numéros d'index de clause associés (dst) et une liste de numéros d'index de clause d'origine (srcs) associés comme variables membres. De plus, lisez le résultat de l'analyse de CaboCha du texte d'entrée, exprimez une phrase sous forme d'une liste d'objets Chunk et affichez la chaîne de caractères et le contact de la phrase de la huitième phrase. Pour le reste des problèmes du chapitre 5, utilisez le programme créé ici.

Sur la base de l'esprit orienté objet, il est préférable de créer une classe «Phrase» car elle a une relation a-une de phrase-> clause-> morphologie. Certes, même si vous la créez, la variable d'instance n'est que «self.chunks», et elle n'est pas indispensable car elle ne change pas grand-chose du point de vue de la gestion des variables.

Cependant, comme srcs ne peut pas être défini tant que le résultat de l'analyse d'une phrase n'est pas lu jusqu'à la fin, il semble naturel de le faire lors de l'initialisation de l'objet Sentence et du traitement au niveau de la phrase (lecture du fichier Cabocha, nième phrase) Je pense que c'est un avantage de pouvoir séparer la lecture du résultat de l'analyse, la réception de n comme argument de ligne de commande) et le traitement au niveau de l'instruction (problèmes ultérieurs, en particulier les questions posées dans 43 et plus tard).

Ce qui suit est un exemple de réponse, mais il comprend les réponses aux questions suivantes. Comme il est décrit dans * docstring *, sautez le code sans rapport avec le n ° 41 et référez-vous-y plus tard.

q41.py


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

from q40 import Morph, arg_int


class Chunk:
    """Lire des clauses à partir d'un fichier au format de treillis cabocha"""
    __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):
        """Ajoutez l'index de clause d'origine. Phrase.__init__()Utilisé dans."""
        self.srcs.append(src_idx)
    
    def morphs_append(self, line):
        """Ajoutez de la morphologie. Phrase.__init__()Utilisé dans."""
        self.morphs.append(Morph(line))
    
    def tostr(self):
        """Renvoie la forme de surface de la clause avec les symboles supprimés. Utilisé dans q42 ou version ultérieure."""
        return ''.join([morph.surface for morph in self.morphs if morph.pos != 'symbole'])
    
    def contain_pos(self, pos):
        """Renvoie si une partie de la phrase existe. Utilisé dans q43 ou version ultérieure."""
        return pos in (morph.pos for morph in self.morphs)

    def replace_np(self, symbol):
        """Remplacez la nomenclature dans la phrase par symbole. Pour q49."""
        morph_lis = []
        for pos, morphs in groupby(self.morphs, key=lambda x: x.pos):
            if pos == 'nom':
                for morph in morphs:
                    morph_lis.append(symbol)
                    break
            elif pos != 'symbole':
                for morph in morphs:
                    morph_lis.append(morph.surface)
        return ''.join(morph_lis)
        
    
class Sentence:
    """Lisez une déclaration du fichier de format de treillis cabocha."""
    __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):
        """Générer une instance de phrase à partir d'un fichier au format de treillis cabocha"""
        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.Afficher l'index de la clause d'origine et l'index de la clause de destination"""
        for chunk in self.chunks:
            print('{}:{} => {}'.format(chunk.idx, chunk, chunk.dst))
    
    def print_dep(self):
        """q42.Afficher la couche de surface de la clause d'origine et de la clause associée séparés par des tabulations"""
        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.Extraire les clauses contenant la nomenclature liée aux clauses contenant des verbes"""
        for chunk in self.chunks:
            if chunk.contain_pos('nom') and self.chunks[chunk.dst].contain_pos('verbe'):
                print('{}\t{}'.format(chunk.tostr(), self.chunks[chunk.dst].tostr()))
                
    def dep_edge(self):
        """Pour faire de la sortie pydot une dépendance avec q44"""
        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.Extraction de modèles de cas verbaux"""
        for chunk in self.chunks:
            for morph in chunk.morphs:
                if morph.pos == 'verbe':
                    verb = morph.base
                    particles = [] #Liste des mots auxiliaires
                    for src in chunk.srcs:
                        #Ajouter le mot auxiliaire le plus à droite dans le segment
                        particles.extend([word.base for word in self.chunks[src].morphs 
                                             if word.pos == 'Particule'][-1:])
                    particles.sort()
                    print('{}\t{}'.format(verb, ' '.join(particles)))
                    #Je n'utilise que le verbe le plus à gauche, donc je peux sortir rapidement
                    break

    def pred_case_arg(self):
        """q46.Extraction d'informations sur le cadre de la casse des verbes"""
        for chunk in self.chunks:
            for morph in chunk.morphs:
                if morph.pos == 'verbe':
                    verb = morph.base
                    particle_chunks = []
                    for src in chunk.srcs:
                        # (Particule,Couche de surface du segment d'origine)
                        particle_chunks.extend([(word.base, self.chunks[src].tostr()) 
                                                for word in self.chunks[src].morphs if word.pos == 'Particule'][-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.Exploration de la syntaxe des verbes fonctionnels"""
        #Drapeau pour l'extraction du nez sa-poule + verbe
        sahen_flag = 0
        for chunk in self.chunks:
            for morph in chunk.morphs:
                if sahen_flag == 0 and morph.pos1 == 'Changer de connexion':
                    sahen_flag = 1
                    sahen = morph.surface
                elif sahen_flag == 1 and morph.base == 'À' and morph.pos == 'Particule':
                    sahen_flag = 2
                elif sahen_flag == 2 and morph.pos == 'verbe':
                    sahen_wo = sahen + 'À'
                    verb = morph.base
                    particle_chunks = []
                    for src in chunk.srcs:
                        # (Particule,Couche de surface du segment d'origine)
                        particle_chunks.extend([(word.base, self.chunks[src].tostr()) for word in self.chunks[src].morphs 
                                         if word.pos == 'Particule'][-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.Suivre les chemins de dépendance depuis les clauses contenant la nomenclature jusqu'à la racine"""
        path = []
        for chunk in self.chunks:
            if chunk.contain_pos('nom'):
                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.Extraction de chemins de dépendances entre nomenclature"""
        #Liste de Chunk list montrant le chemin de la clause contenant la nomenclature à la racine (1 phrase)
        all_paths = list(self.trace_dep_path())
        arrow = ' -> '
        #Liste des ensembles d'identifiants de clause pour chaque chemin
        all_paths_set = [{chunk.idx for chunk in chunks} for chunks in all_paths]
        # all_Sélectionnez une paire parmi les chemins
        for p1, p2 in combinations(range(len(all_paths)), 2):
            #Trouver la phrase courante 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]))
            #La partie commune n'est pas vide et n'est pas non plus un sous-ensemble
            if 0 < len_intersec < len_smaller:
                #Afficher le chemin
                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)]))
        #Rechercher et afficher des motifs liés à la nomenclature à partir de la nomenclature
        for chunks in all_paths:
            for j in range(1, len(chunks)):
                if chunks[j].contain_pos('nom'):
                    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: Ceci => 1 1: un élève est => 7 2: Parfois => 4 3: Nous => 4 4: Capture => 5 5: ébullition => 6 6: Manger => 7 7: C'est une histoire. => -1

Puisque le but de ce chapitre est d'apprendre la définition de la classe, je vais sauter l'explication des problèmes suivants.

Supplément aux méthodes spéciales

C'est une petite histoire avancée, vous pouvez donc la sauter.

Ce qui est inquiétant dans le code de la classe «Sentence», c'est que les descriptions telles que «pour chunk dans self.chunks» et «self.chunks [i]» sont fréquentes. Si vous définissez la méthode spéciale suivante, vous pouvez utiliser for chunk in self ou self [i].

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

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

En fait, en tournant la liste avec l'instruction for, elle était convertie en interne en itérateur par la fonction ʻiter () . Et la fonction ʻiter () appelle la méthode __iter () __ de l'objet. Par conséquent, si vous l'écrivez comme ceci, vous pouvez activer l'instance Sentence elle-même avec une instruction for.

L'accès à l'index est possible en définissant __getitem__ ().

De plus, si la classe Chunk est définie de la même manière, l'instruction for suivante peut être envoyée à l'instance.

for chunk in sentence:
    for morph in chunk:

Peut-être que le wrapper Python pour l'analyseur de dépendances dans le monde est également fait comme ceci. De plus, je pense qu'il est plus naturel de définir les méthodes pour q41 et 42 dans la classe Chunk, d'écrire l'instruction for ci-dessus à l'extérieur et de l'appeler.

42 et 43 sont omis car il n'y a rien de spécial à dire.

44. Visualisation des arbres dépendants

Visualisez l'arbre de dépendance d'une phrase donnée sous forme de graphe orienté. Pour la visualisation, il est conseillé de convertir l'arborescence de dépendances dans le langage DOT et d'utiliser Graphviz. De plus, pour visualiser des graphiques dirigés directement à partir de Python, utilisez pydot.

Je ferai de mon mieux pour installer Graphviz et pydot. Beaucoup de gens disent que pydot-ng devrait être utilisé parce que pydot n'a pas été maintenu depuis longtemps, mais dans mon environnement, cela fonctionnait avec pydot, donc j'utilise pydot. Je n'ai utilisé que du pydot avec 100 coups, donc je ne vais pas l'expliquer en particulier. Pour la mise en œuvre, j'ai fait référence à ce blog.

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 gothique"
            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

La direction de la flèche de dépendance peut être inversée de cette manière (surtout si elle est basée sur des dépendances universelles), mais je pense que l'une ou l'autre convient parfaitement à ce problème.

(En raison de la spécification de graph_from_edges (), s'il y a plusieurs clauses avec la même couche de surface (et des identifiants différents) dans l'instruction, elles seront considérées comme le même nœud. Pour éviter cela, graph_from_edges () Il est nécessaire de modifier l'implémentation ou d'ajouter un identifiant au système de couches de surface.)

(La police est MS P Gothic car mon environnement est Ubuntu avec WSL et je n'ai pas pu trouver la police japonaise, j'ai donc utilisé la technique de référencement de la police Windows.)

45. Extraction de modèles de cas verbaux

Je voudrais considérer la phrase utilisée cette fois comme un corpus et enquêter sur les cas possibles de prédicats japonais. Considérez le verbe comme un prédicat et le verbe auxiliaire de la phrase liée au verbe comme une casse, et affichez le prédicat et la casse dans un format délimité par des tabulations. Cependant, assurez-vous que la sortie répond aux spécifications suivantes.

--Dans une clause contenant un verbe, la forme de base du verbe le plus à gauche est utilisée comme prédicat. --La casse est le mot auxiliaire lié au prédicat --S'il existe plusieurs mots auxiliaires (phrases) liés au prédicat, arrangez tous les mots auxiliaires dans l'ordre du dictionnaire, séparés par des espaces.

Prenons l'exemple de la phrase (8ème phrase de neko.txt.cabocha) que "j'ai vu un être humain pour la première fois ici". Cette phrase contient deux verbes, «commencer» et «voir», et la phrase liée à «commencer» est analysée comme «ici», et la phrase liée à «voir» est analysée comme «je suis» et «chose». Devrait produire la sortie suivante.

Voir `` ''

Enregistrez la sortie de ce programme dans un fichier et vérifiez les éléments suivants à l'aide des commandes UNIX.

--Combinaison de prédicats et de modèles de cas qui apparaissent fréquemment dans le corpus
--Le modèle de casse des verbes "faire", "voir" et "donner" (organiser par ordre de fréquence d'apparition dans le corpus)

Si vous souhaitez enquêter sur le cas, vous devez vous concentrer sur l'assistant de cas, mais suivez l'énoncé du problème qui pleure. Disposez les mots auxiliaires dans l'ordre lexical. Il n'est pas écrit dans l'énoncé du problème s'il faut ou non sortir le cas où il n'y a pas de mot auxiliaire dans le greffier du prédicat, mais comme c'est un cas où le cas est omis, j'ai décidé de l'agréger.

«Sorted ()» et «list.sorted ()» de Python peuvent également trier les chaînes de caractères, mais ils sont dans l'ordre du dictionnaire basé sur les points de code et sont différents de l'ordre alphabétique utilisé dans les dictionnaires japonais. Sachons.

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

[«Kakashi», «Kakaku»]

L'exemple de solution ci-dessus utilise ʻextend () `. Une méthode qui concatène des listes avec le même résultat que «+». Veillez à ne pas confondre «list.append» avec «list.extend».

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

Être né Tsukuka Faire En pleurant Faire Au début À voir Ecoutez Attraper Ébullition

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

704 452 435 333 je pense 202 Devenir 199 188 175 Regardez 159 122 disent 117 113 108 98 voir 97 Quand tu vois 94 90 89 85 80 voir

!python q45.py < neko.txt.cabocha | grep -E "^(Faire|à voir|donner)\s" | sort | uniq -c | sort -nr | head -20

452 435 188 175 Regardez 159 117 113 98 voir 90 85 80 voir 61 60 60 51 51 à partir de 46 40 39 Qu'est-ce que 37

Jugement de valeur de vérité

Puisque la description telle que «si liste:» est utilisée dans l'exemple de réponse du n ° 46, je vais expliquer le jugement de valeur de vérité. Tout objet autre que les expressions conditionnelles peut être écrit dans l'instruction if, et qu'advient-il du jugement de valeur de vérité dans ce cas? Documentation (/stdtypes.html#truth), veuillez donc vous y référer. En bref, «Aucun», les choses semblables à zéro et «x» avec «len (x) == 0» sont tous traités comme False, et les autres sont traités comme True. Sachant cela, il est facile d'écrire des choses comme "quand la liste n'est pas vide". Si vous n'êtes pas sûr, essayez d'appliquer la fonction intégrée bool () à divers objets.

Il n'y a rien d'autre à dire, donc 46 est omis.

47. Exploration de la syntaxe des verbes fonctionnels

Je voudrais prêter attention uniquement au cas où le verbe wo case contient une nomenclature de connexion sa-hen. Modifiez 46 programmes pour répondre aux spécifications suivantes.

-Uniquement lorsque la phrase consistant en "connexion sahen nom + (verbe auxiliaire)" est liée au verbe -Le prédicat est "nom de connexion Sahen + est la forme de base du + verbe", et s'il y a plusieurs verbes dans la phrase, le verbe le plus à gauche est utilisé. -S'il y a plusieurs mots auxiliaires (phrases) liés au prédicat, arrangez tous les mots auxiliaires dans l'ordre du dictionnaire séparés par des espaces. -S'il y a plusieurs clauses liées au prédicat, arranger tous les termes séparés par des espaces (aligner avec l'ordre des mots auxiliaires).

Par exemple, la sortie suivante doit être obtenue à partir de la phrase «Le maître répondra à la lettre, même si elle arrive à un autre point».

Lorsque vous répondez, le maître est `` ''

Enregistrez la sortie de ce programme dans un fichier et vérifiez les éléments suivants à l'aide des commandes UNIX. -Prédicats qui apparaissent fréquemment dans le corpus (nomenclature de connexion sahénienne + + verbe)

  • Motif prédictif et auxiliaire qui apparaît fréquemment dans le corpus

La nomenclature de connexion Sa-poule est une nomenclature qui peut être transformée en une variable sa en ajoutant "à" à la fin, comme "réponse". À propos, dans la grammaire scolaire, «réponse» est un mot, mais dans l'analyse morphologique, il est généralement divisé comme «réponse / réponse».

De plus, pardonnez-moi que l'exemple de réponse est un terrible code push ...

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

30 réponse 21 Dites bonjour 14 imiter 13 parler 13 querelle 6 Faites une sieste 5 exercice 5 Poser une question 5 Poser une question 5 Écoutez l'histoire

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

8 imiter 6 Lorsque vous répondez 6 querelle 4 exercice 4 Répondre 4 Répondre 4 Écoutez l'histoire 4 Quand tu dis bonjour 4 Je vais dire bonjour 3 Pour poser une question

48. Extraction des chemins de la nomenclature aux racines

Pour une clause contenant toute la nomenclature de la phrase, extrayez le chemin de cette clause jusqu'à la racine de l'arbre de syntaxe. Cependant, le chemin sur l'arbre de syntaxe doit satisfaire aux spécifications suivantes. -Chaque clause est représentée par une séquence morphologique (superficielle) -Concaténer les expressions de chaque clause avec -> "` `` de la clause de début à la clause de fin du chemin

A partir de la phrase "J'ai vu un être humain pour la première fois ici" (8ème phrase de neko.txt.cabocha), le résultat suivant devrait être obtenu.

je suis->vu
ici->Commencer avec->Humain->Des choses->vu
Humain->Des choses->vu
Des choses->vu

Tout ce que vous avez à faire est de suivre la destination avec while ou récursif. Compte tenu du problème suivant, j'ai créé une méthode uniquement pour le traitement jusqu'à une étape avant la sortie.

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

J'ai vu-> Ici-> pour la première fois-> humain-> j'ai vu quelque chose-> J'ai vu un humain-> chose-> J'ai vu quelque chose->

(Ajouté le 18/05/2020) Après avoir reçu des commentaires, j'ai remarqué que l'exemple d'analyse des dépendances de l'énoncé du problème était une erreur d'analyse. Il est prévu de corriger les exemples de phrases utilisés dans issue. Le but principal du chapitre 5 de 100 coups est de pratiquer la définition de classe, et je pense que la raison pour laquelle CaboCha est spécifié est simplement parce qu'il est facile à utiliser. D'autre part, il est également envisagé de concevoir un énoncé de problème pour donner à l'analyseur de dépendances une plage plus large. GiNZA est également facile à utiliser de nos jours.

49. Extraction de chemins de dépendance entre nomenclature

Extraire le chemin de dépendance le plus court qui relie toutes les paires de nomenclatures de la phrase. Cependant, le numéro de clause de la paire de nomenclature est i Lorsque> et j (i <j), le chemin de dépendance doit satisfaire aux spécifications suivantes.

-Similaire au problème 48, le chemin est exprimé en concaténant les expressions (éléments morphologiques de surface) de chaque phrase de la clause de début à la clause de fin avec "->". -Remplacer la nomenclature contenue dans les clauses i et j par X et Y, respectivement.

De plus, la forme du chemin de dépendance peut être considérée des deux manières suivantes.

-Si la clause j est sur le chemin de la clause i à la racine de l'arbre syntaxique: Afficher le chemin de la clause i à la clause j -Autre de ce qui précède, lorsque la clause i et la clause j se croisent en une clause commune k sur le chemin de la clause j à la racine de l'arbre de syntaxe: le chemin immédiatement avant la clause i vers la clause k et le chemin de la clause j vers la clause juste avant la clause k> Afficher le contenu de la clause k en les concaténant avec "|"

Par exemple, à partir de la phrase "J'ai vu un être humain pour la première fois ici" (8ème phrase de neko.txt.cabocha), la sortie suivante devrait être obtenue.

X est|En Y->Commencer avec->Humain->Des choses|vu
X est|Appelé Y->Des choses|vu
X est|Oui|vu
En X->Commencer avec-> Y
En X->Commencer avec->Humain-> Y
Appelé X-> Y

À propos, c'est le problème le plus difficile dans la version 2020 de 100 coups. Tout d'abord, l'énoncé du problème est très difficile à comprendre. Et même si vous en comprenez le sens, c'est toujours difficile. Pour le moment, comprenons simplement la signification de l'énoncé du problème.

En bref, le problème est de convertir la sortie du problème 48 en quelque chose comme ça.

Cependant, il y a trois pièges sous l'exemple de sortie qui correspondent à "Quand la clause j existe sur le chemin de la clause i à la racine de l'arborescence de syntaxe: Afficher le chemin de la clause i à la clause j". Heureusement, cette partie semble facile à mettre en œuvre. Puisque le chemin en question 48 est un début de nomenclature, la première clause des quatre chemins est un candidat pour i. Tout ce que vous avez à faire est de rechercher j à partir de là. Et enfin, remplacez la nomenclature de i et j par X et Y (je pense que «X-> Y est» au lieu de «X-> Y», mais c'est gogogogogogo).

La partie qui semble difficile est «autre que ce qui précède». C'est "je suis->"J'ai vu" et "humain->Des choses->S'il y a un chemin comme "je l'ai vu", combinez-le et dites "je suis|Humain->Des choses|Comme "j'ai vu", une autre clause i,Remplacez la nomenclature de j par "X est|Appelé Y->Des choses|"Je l'ai vu."

Il y a quatre chemins dans le problème 48, vous devez donc en choisir deux et exécuter la boucle 4C2 fois. C'est douloureux si vous ne connaissez pas ʻitertools.combinations () `. Et si vous n'ignorez pas les paires qui sont dans une relation de sous-ensemble, comme "Humain-> J'ai vu quelque chose->" et "J'ai vu quelque chose->", ce serait mauvais. Ce processus est ennuyeux. En outre, pour les clauses i et j, la première clause des deux chemins est un candidat, mais le processus de recherche d'une phrase commune k à partir de là et le processus de création d'une chaîne de sortie après sa découverte sont gênants. ~~ Ce n'est ni une introduction au traitement du langage naturel ni une introduction à Python, donc je ne pense pas qu'il soit nécessaire de le forcer.

Résumé

--Classe

en conclusion

Cela complète la série d'introduction à Python avec 100 coups de traitement du langage?

Recommended Posts

[Chapitre 5] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 3] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 2] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 4] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 6] Introduction à scicit-learn avec 100 coups de traitement du langage
100 traitements de langage avec Python
100 traitements de langage avec Python (chapitre 3)
100 traitements de langage avec Python (chapitre 2, partie 2)
100 traitements de langage avec Python (chapitre 2, partie 1)
100 coups de traitement du langage ~ Chapitre 1
Introduction au langage Python
Le traitement de 100 langues frappe le chapitre 2 (10 ~ 19)
Démarrer avec Python avec 100 coups sur le traitement du langage
100 coups de traitement du langage avec Python 2015
100 Language Processing Knock Chapitre 1 (Python)
100 Language Processing Knock Chapitre 2 (Python)
Réhabilitation des compétences Python et PNL à partir de "100 Language Processing Knock 2015" (Chapitre 1)
[Traitement du langage 100 coups 2020] Résumé des exemples de réponses par Python
Introduction au traitement parallèle distribué Python par Ray
J'ai essayé de résoudre la version 2020 de 100 problèmes de traitement du langage [Chapitre 3: Expressions régulières 20 à 24]
J'ai essayé de résoudre la version 2020 de 100 coups de traitement de langue [Chapitre 1: Mouvement préparatoire 00-04]
J'ai essayé de résoudre la version 2020 de 100 traitements linguistiques [Chapitre 1: Mouvement préparatoire 05-09]
[Traitement du langage 100 coups 2020] Chapitre 3: Expressions régulières
100 traitements du langage naturel frappent le chapitre 4 Commentaire
[Traitement du langage 100 coups 2020] Chapitre 6: Machine learning
100 Language Processing Knock Chapitre 1 en Python
100 coups de traitement du langage 2020: Chapitre 4 (analyse morphologique)
[Traitement du langage 100 coups 2020] Chapitre 5: Analyse des dépendances
[Introduction à Python3 Jour 13] Chapitre 7 Chaînes de caractères (7.1-7.1.1.1)
[Introduction à Python3 Jour 14] Chapitre 7 Chaînes de caractères (7.1.1.1 à 7.1.1.4)
Introduction à Protobuf-c (langage C ⇔ Python)
[Traitement du langage 100 coups 2020] Chapitre 1: Mouvement préparatoire
Traitement d'image avec la binarisation Python 100 knocks # 3
[Traitement du langage 100 coups 2020] Chapitre 7: Vecteur Word
[Introduction à Python3 Jour 15] Chapitre 7 Chaînes de caractères (7.1.2-7.1.2.2)
10 fonctions du "langage avec batterie" python
100 Language Processing Knock 2020: Chapitre 3 (expression régulière)
[Traitement du langage 100 coups 2020] Chapitre 8: Réseau neuronal
[Traitement du langage 100 coups 2020] Chapitre 2: Commandes UNIX
[Traitement du langage 100 coups 2020] Chapitre 9: RNN, CNN
100 traitement d'image par Python Knock # 2 Échelle de gris
100 Language Processing Knock Chapitre 1 par Python
[Traitement du langage 100 coups 2020] Chapitre 4: Analyse morphologique
[Introduction à Python3 Day 21] Chapitre 10 Système (10.1 à 10.5)
[Raspi4; Introduction au son] Enregistrement stable de l'entrée sonore avec python ♪
Réhabilitation des compétences Python et PNL à partir de «Knock 100 Language Processing 2015» (chapitre 2 deuxième semestre)
Réhabilitation des compétences Python et PNL à partir de "100 Language Processing Knock 2015" (Chapitre 2 premier semestre)
100 traitements linguistiques frappent 03 ~ 05
100 coups de traitement linguistique (2020): 40
Bases du traitement d'images binarisées par Python
100 coups de traitement linguistique (2020): 32
Résumé du chapitre 2 de l'introduction aux modèles de conception appris en langage Java
Système de notation IPynb réalisé avec TA d'introduction à la programmation (Python)
J'ai fait 100 traitements linguistiques Knock 2020 avec GiNZA v3.1 Chapitre 4
100 coups de traitement linguistique (2020): 35
100 coups de traitement linguistique (2020): 47
100 coups de traitement linguistique (2020): 39
Chapitre 4 Résumé de l'introduction aux modèles de conception appris en langage Java
100 coups de traitement linguistique (2020): 22