Traitement du langage 100 coups 2015 ["Chapitre 5: Analyse des dépendances"](http: //www.cl.ecei. Il s'agit d'un enregistrement de 49ème "Extraction de chemin de dépendance entre nomenclature" de tohoku.ac.jp/nlp100/#ch5). .. C'est finalement le tournant de 100 coups. Voici ** un coup vicieux qui devient une porte démoniaque **. ** Le problème est difficile à comprendre, et même si vous le comprenez, il est difficile de le résoudre **. J'ai l'impression d'avoir terminé 100, mais c'est le coup le plus gênant pour mon propre algorithme (il y en a d'autres plus compliqués, mais je laisse fondamentalement cela au paquet, donc je n'ai pas beaucoup de problèmes).
| Lien | Remarques | 
|---|---|
| 049.Extraction de chemins de dépendances entre nomenclature.ipynb | Lien GitHub du programme de réponse | 
| 100 coups de traitement du langage amateur:49 | Copiez et collez la source de nombreuses pièces source | 
| Officiel de CaboCha | Page CaboCha à regarder en premier | 
J'ai installé CRF ++ et CaboCha il y a trop longtemps et j'ai oublié comment les installer. Puisqu'il s'agit d'un package qui n'a pas du tout été mis à jour, nous n'avons pas reconstruit l'environnement. Je me souviens seulement d'avoir été frustré lorsque j'ai décidé d'utiliser CaboCha sous Windows. Je pense que je ne pourrais pas l'utiliser sur Windows 64 bits (j'ai une mémoire vague et peut-être qu'il y a un problème avec ma capacité technique).
| type | version | Contenu | 
|---|---|---|
| OS | Ubuntu18.04.01 LTS | Il fonctionne virtuellement | 
| pyenv | 1.2.16 | J'utilise pyenv car j'utilise parfois plusieurs environnements Python | 
| Python | 3.8.1 | python3 sur pyenv.8.J'utilise 1 Les packages sont gérés à l'aide de venv  | 
| Mecab | 0.996-5 | apt-Installer avec get | 
| CRF++ | 0.58 | C'est trop vieux et j'ai oublié comment l'installer(Peut-êtremake install) | 
| CaboCha | 0.69 | C'est trop vieux et j'ai oublié comment l'installer(Peut-êtremake install) | 
Appliquer l'analyseur de dépendances CaboCha à "Je suis un chat" et expérimenter le fonctionnement de l'arbre de dépendances et l'analyse syntaxique.
Classe, Analyse des dépendances, CaboCha, Clause, Dépendance, Cas, Syntaxe des verbes fonctionnels, Chemin des dépendances, [Graphviz](http: / /www.graphviz.org/)
Utilisation de CaboCha pour le texte (neko.txt) du roman de Natsume Soseki "Je suis un chat" Analysez la dépendance et enregistrez le résultat dans un fichier appelé neko.txt.cabocha. Utilisez ce fichier pour implémenter un programme qui répond aux questions suivantes.
Extraire le chemin de dépendance le plus court qui relie toutes les paires de nomenclatures de la phrase. Cependant, lorsque les numéros de clause de la paire de nomenclature sont * i * 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 présente sur le chemin de la clause * i * à la racine de l'arbre de syntaxe: Afficher le chemin de la clause * i * à la clause * j * --Autre de ce qui précède, lorsque la phrase * 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 et la clause * immédiatement avant la clause * i * vers la clause * k * Le chemin de j * à juste avant la clause * k * et le contenu de la clause * k * sont concaténés avec "
|" et affichésPar 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 Avec X->Commencer avec-> Y Avec X->Commencer avec->Humain-> Y Appelé X-> Y
Le contenu du coup est long et difficile à comprendre. Article ["Traitement du langage amateur 100 coups: 49"](https://qiita.com/segavvy/items/dfbf9d5dd5885e807d49#%E5%95%8F%E9%A1%8C%E3%81%AE%E6% J'ai finalement compris après avoir vu 84% 8F% E5% 91% B3). Je vais omettre mon explication ...
Il y a 150 lignes ... Il semble que cela puisse être fait un peu plus simple si cela prend du temps, mais je suis cassé.
import re
#Délimiteur
separator = re.compile('\t|,')
#Dépendance
dependancy = re.compile(r'''(?:\*\s\d+\s) #Non soumis à la capture
                            (-?\d+)       #Nombres(Contact)
                          ''', re.VERBOSE)
NOUN_REPLACE = '@@noun@@'
SEP1 = ' -> '
SEP2 = ' | '
class Morph:
    def __init__(self, line):
        
        #Diviser par tabulation et virgule
        cols = separator.split(line)
        
        self.surface = cols[0] #Type de surface(surface)
        self.base = cols[7]    #Forme basique(base)
        self.pos = cols[1]     #Partie(pos)
        self.pos1 = cols[2]    #Sous-classification des paroles des parties 1(pos1)
class Chunk:
    def __init__(self, morphs, dst):
        self.morphs = morphs
        self.dst  = dst  #Numéro d'index de la clause de contact
        self.dsts = []
        
        self.phrase = ''
        self.phrase_noun = ''
        self.noun = False
        
        for morph in morphs:
            if morph.pos != 'symbole':
                self.phrase += morph.surface #Pour les non-symboles
                if morph.pos == 'nom':
                    
                    #S'il s'agit du premier nez, traitez-le comme une expression de nomenclature
                    if self.noun == False:
                        self.noun = True
                        self.phrase_noun += NOUN_REPLACE
                        continue
                    
                    #Si le précédent est une nomenclature, ne remplacez pas le système de surface
                    elif self.noun and self.phrase_noun.endswith(NOUN_REPLACE):
                        continue
                        
                #Sauf pour le remplacement et le saut, ajoutez le système de surface tel quel
                self.phrase_noun += morph.surface
morphs = []
chunks = []
sentences = []
with open('./neko.txt.cabocha') as f:
    
    for line in f:
        dependancies = dependancy.match(line)
        
        #S'il ne s'agit pas d'EOS ou du résultat de l'analyse des dépendances
        if not (line == 'EOS\n' or dependancies):
            morphs.append(Morph(line))
            
        #Lorsqu'il y a une analyse morphologique, le résultat de l'analyse EOS ou des dépendances
        elif len(morphs) > 0:
            chunks.append(Chunk(morphs, dst))
            morphs = []
       
        #En cas de dépendance résultat
        if dependancies:
            dst = int(dependancies.group(1))
        
        #Lorsqu'il y a un résultat de dépendance dans EOS
        if line == 'EOS\n' and len(chunks) > 0:
            
            #Ajouter des contacts(Ajoutez la dernière ligne pour plus d'efficacité)
            for chunk in chunks[::-1]:
                if chunk.dst!= -1:
                    chunk.dsts.extend([chunk.dst])
                    if chunks[chunk.dst].dst != -1:
                        chunk.dsts.extend(chunks[chunk.dst].dsts)
            sentences.append(chunks)
            chunks = []
def output_result(i, chunk, sentence):
    print('\tX:', i, chunk.phrase, chunk.dsts)
    
    #Nomenclature(X)Boucle pour rechercher Y après
    for i_y, y_chunk in enumerate(sentence[i+1:], i+1):
        
        #Y pour nomenclature
        if y_chunk.noun:
            
            #Sortie d'informations Y
            print('\t\tY:', i_y, y_chunk.phrase)
            
            result = ''
            
            #Y est la route(Contact)Si inclus dans
            if i_y in chunk.dsts:
                
                result = '\t\t\tP1:' + '\t' + sentence[i].phrase_noun.replace(NOUN_REPLACE, 'X') + SEP1
                
                #Sortie de X à Y
                for i_path in chunk.dsts:
                    
                    #Terminer lorsque Y est atteint
                    if i_path == i_y:
                        result += 'Y'
                        break
                    else:
                        result += sentence[i_path].phrase + SEP1
            
            #Y est la route(Contact)Si non inclus dans
            else:
                result = '\t\t\tP2:' + '\t' + sentence[i].phrase_noun.replace(NOUN_REPLACE, 'X')
                
                #Rechercher une clause commune avec la valeur minimale dans l'ensemble de produits
                i_goal = min(set(chunk.dsts).intersection(set(sentence[i_y].dsts)))
                
                #De X à Y avant la phrase courante
                for i_path in chunk.dsts:                    
                    if i_path == i_goal:
                        result += SEP2 + sentence[i_y].phrase_noun.replace(NOUN_REPLACE, 'Y')
                        break
                    else:
                        result += SEP1 + sentence[i_path].phrase                 
                
                #De la personne en charge de Y à la phrase courante
                for i_path in sentence[i_y].dsts:
                    if i_path == i_goal:
                        result += SEP2 + sentence[i_goal].phrase
                    else:
                        result += SEP1 + sentence[i_path].phrase
            print(result)
for i_sentence, sentence in enumerate(sentences):
    
    #Sortie de déclaration
    print(i_sentence, '\t', ''.join([chunk.phrase for chunk in sentence]))
    for i_chunk, chunk in enumerate(sentence):
        
        #Si la phrase est une nomenclature
        if chunk.noun and chunk.dst != -1:
            output_result(i_chunk, chunk, sentence)
    #Limité car il y en a beaucoup
    if i_sentence > 5:
        break
Tout d'abord, nous élargissons considérablement la classe Chunk.
Définissez le drapeau «noun» sur «True» s'il s'agit d'une nomenclature.
Ensuite, créez phrase_noun de sorte que" X est "et" Y est ". À ce stade, cela peut être X ou Y, donc j'ai mis la valeur fixe NOUN_REPLACE dans les parties X et Y. En outre, en considérant le cas où la nomenclature est continue (exemple: colombe + horloge), ʻend avecest vérifié pour voir si la fin estNOUN_REPLACE (assurez-vous que NOUN_REPLACE` n'est pas continu).
python
class Chunk:
    def __init__(self, morphs, dst):
        self.morphs = morphs
        self.dst  = dst  #Numéro d'index de la clause de contact
        self.dsts = []
        
        self.phrase = ''
        self.phrase_noun = ''
        self.noun = False
        
        for morph in morphs:
            if morph.pos != 'symbole':
                self.phrase += morph.surface #Pour les non-symboles
                if morph.pos == 'nom':
                    
                    #S'il s'agit du premier nez, traitez-le comme une expression de nomenclature
                    if self.noun == False:
                        self.noun = True
                        self.phrase_noun += NOUN_REPLACE
                        continue
                    
                    #Si le précédent est une nomenclature, ne remplacez pas le système de surface
                    elif self.noun and self.phrase_noun.endswith(NOUN_REPLACE):
                        continue
                        
                #Sauf pour le remplacement et le saut, ajoutez le système de surface tel quel
                self.phrase_noun += morph.surface
Ce qui a changé depuis la dernière fois, c'est la dernière branche conditionnelle ʻif"lorsqu'il y a un résultat de dépendance dans EOS".  Parcourez l'ensemble des clauseschunks` pour créer une liste de tous les contacts. La raison de la boucle en arrière à partir de la dernière ligne est que nous voulons créer tous les contacts à la fois et réutiliser la liste de contacts une fois créée.
python
for line in f:
    dependancies = dependancy.match(line)
    
    #S'il ne s'agit pas d'EOS ou du résultat de l'analyse des dépendances
    if not (line == 'EOS\n' or dependancies):
        morphs.append(Morph(line))
        
    #Lorsqu'il y a une analyse morphologique, le résultat de l'analyse EOS ou des dépendances
    elif len(morphs) > 0:
        chunks.append(Chunk(morphs, dst))
        morphs = []
   
    #En cas de dépendance résultat
    if dependancies:
        dst = int(dependancies.group(1))
    
    #Lorsqu'il y a un résultat de dépendance dans EOS
    if line == 'EOS\n' and len(chunks) > 0:
        
        #Ajouter des contacts(Ajoutez à partir de la dernière ligne pour plus d'efficacité)
        for chunk in chunks[::-1]:
            if chunk.dst!= -1:
                chunk.dsts.extend([chunk.dst])
                if chunks[chunk.dst].dst != -1:
                    chunk.dsts.extend(chunks[chunk.dst].dsts)
        sentences.append(chunks)
        chunks = []
Pour faciliter la vérification plus tard, la phrase entière, les informations X et Y sont indentées et sorties, bien qu'elles ne soient pas incluses dans les instructions de tâche. La sortie cette fois peut être divisée en gros dans les deux modèles suivants.
--Pattern 1: X et Y sont la même route: Y remplace la clause entière-> Mis à P1 en sortie
--Modèle 2: X et Y sont séparés par un autre chemin: |, Y remplace uniquement la nomenclature -> Mis à P2 au moment de la sortie
Dans le cas du motif 1, puisque X et Y sont le même chemin, X à Y sont sortis dans l'ordre. Le motif 2 est compliqué car X et Y sont des chemins différents. J'utilise la fonction ʻintersection` pour trouver la terminaison partagée de X et Y. "Ce que j'ai fait dans le sixième chapitre du chapitre 1" C'est vrai.
python
def output_result(i, chunk, sentence):
    print('\tX:', i, chunk.phrase, chunk.dsts)
    #Nomenclature(X)Boucle pour rechercher Y après
    for i_y, y_chunk in enumerate(sentence[i+1:], i+1):
        #Y pour nomenclature
        if y_chunk.noun:
            #Sortie d'informations Y
            print('\t\tY:', i_y, y_chunk.phrase)
            result = ''
            #Y est la route(Contact)Si inclus dans
            if i_y in chunk.dsts:
                result = '\t\t\tP1:' + '\t' + sentence[i].phrase_noun.replace(NOUN_REPLACE, 'X') + SEP1
                #Sortie de X à Y
                for i_path in chunk.dsts:
                    #Terminer lorsque Y est atteint
                    if i_path == i_y:
                        result += 'Y'
                        break
                    else:
                        result += sentence[i_path].phrase + SEP1
            #Y est la route(Contact)Si non inclus dans
            else:
                result = '\t\t\tP2:' + '\t' + sentence[i].phrase_noun.replace(NOUN_REPLACE, 'X')
                #Rechercher une clause commune avec la valeur minimale dans l'ensemble de produits
                i_goal = min(set(chunk.dsts).intersection(set(sentence[i_y].dsts)))
                #De X à Y avant la phrase courante
                for i_path in chunk.dsts:                    
                    if i_path == i_goal:
                        result += SEP2 + sentence[i_y].phrase_noun.replace(NOUN_REPLACE, 'Y')
                        break
                    else:
                        result += SEP1 + sentence[i_path].phrase                 
                #De la personne en charge de Y à la phrase courante
                for i_path in sentence[i_y].dsts:
                    if i_path == i_goal:
                        result += SEP2 + sentence[i_goal].phrase
                    else:
                        result += SEP1 + sentence[i_path].phrase
            print(result)
Lorsque le programme est exécuté, les résultats suivants sont affichés. C'est peut-être vrai. Je n'ai pas beaucoup vérifié.
Résultat de sortie
0 un
1 je suis un chat
2 Pas encore de nom
	X:0 Le nom est[2]
3 Je n'ai aucune idée d'où je suis né
	X:0 où[1, 2, 4]
		Y:2 Katonto
			P1:Avec X->Née-> Y
		Y:3 J'ai un indice
			P2:Avec X->Née->Katonto|Oui|Ne pas utiliser
	X:2 Katonto[4]
		Y:3 J'ai un indice
			P2:Avec X|Oui|Ne pas utiliser
	X:3 J'ai un indice[4]
4 Je me souviens d'avoir pleuré seulement dans un endroit sombre et humide
	X:0 rien[1, 5, 7]
		Y:À 3
			P2:Même avec X->faible|En Y|En larmes->je me rappelle
		Y:6 Seulement ce qui était là
			P2:Même avec X->faible->En larmes|Seulement Y|je me rappelle
		Y:7 Souviens-toi
			P1:Même avec X->faible->En larmes-> Y
	X:À 3[5, 7]
		Y:6 Seulement ce qui était là
			P2:Avec X->En larmes|Seulement Y|je me rappelle
		Y:7 Souviens-toi
			P1:Avec X->En larmes-> Y
	X:6 Seulement ce qui était là[7]
		Y:7 Souviens-toi
			P1:Seulement X-> Y
5 J'ai vu des êtres humains pour la première fois ici
	X:0 je suis[5]
		Y:1 où
			P2:X est|En Y->Commencer avec->Humain->Des choses|vu
		Y:3 humain
			P2:X est|Appelé Y->Des choses|vu
		Y:4 choses
			P2:X est|Oui|vu
	X:1 où[2, 3, 4, 5]
		Y:3 humain
			P1:Avec X->Commencer avec-> Y
		Y:4 choses
			P1:Avec X->Commencer avec->Humain-> Y
	X:3 humain[4, 5]
		Y:4 choses
			P1:Appelé X-> Y
	X:4 choses[5]
De plus, j'ai entendu plus tard que c'était la race la plus méchante des êtres humains appelée Shosei.
	X:1 plus tard[2, 9]
		Y:3 c'est
			P2:Avec X->Quand tu entends|Y est|C'est tout
		Y:4 Appelé un étudiant
			P2:Avec X->Quand tu entends|Appelé Y->Chez l'homme->C'était une course|C'est tout
		Y:5 chez l'homme
			P2:Avec X->Quand tu entends|En Y->C'était une course|C'est tout
		Y:6 Ichiban
			P2:Avec X->Quand tu entends| Y ->Mal->C'était une course|C'est tout
		Y:7 mal
			P2:Avec X->Quand tu entends|Oui->C'était une course|C'est tout
		Y:Était 8 courses
			P2:Avec X->Quand tu entends|Était Y|C'est tout
		Y:9 C'est vrai
			P1:Avec X->Quand tu entends-> Y
	X:3 c'est[9]
		Y:4 Appelé un étudiant
			P2:X est|Appelé Y->Chez l'homme->C'était une course|C'est tout
		Y:5 chez l'homme
			P2:X est|En Y->C'était une course|C'est tout
		Y:6 Ichiban
			P2:X est| Y ->Mal->C'était une course|C'est tout
		Y:7 mal
			P2:X est|Oui->C'était une course|C'est tout
		Y:Était 8 courses
			P2:X est|Était Y|C'est tout
		Y:9 C'est vrai
			P1:X est-> Y
	X:4 Appelé un étudiant[5, 8, 9]
		Y:5 chez l'homme
			P1:Appelé X-> Y
		Y:6 Ichiban
			P2:Appelé X->Chez l'homme| Y ->Mal|C'était une course->C'est tout
		Y:7 mal
			P2:Appelé X->Chez l'homme|Oui|C'était une course->C'est tout
		Y:Était 8 courses
			P1:Appelé X->Chez l'homme-> Y
		Y:9 C'est vrai
			P1:Appelé X->Chez l'homme->C'était une course-> Y
	X:5 chez l'homme[8, 9]
		Y:6 Ichiban
			P2:Avec X| Y ->Mal|C'était une course->C'est tout
		Y:7 mal
			P2:Avec X|Oui|C'était une course->C'est tout
		Y:Était 8 courses
			P1:Avec X-> Y
		Y:9 C'est vrai
			P1:Avec X->C'était une course-> Y
	X:6 Ichiban[7, 8, 9]
		Y:7 mal
			P1:	X -> Y
		Y:Était 8 courses
			P1:	X ->Mal-> Y
		Y:9 C'est vrai
			P1:	X ->Mal->C'était une course-> Y
	X:7 mal[8, 9]
		Y:Était 8 courses
			P1:X-> Y
		Y:9 C'est vrai
			P1:X->C'était une course-> Y
	X:Était 8 courses[9]
		Y:9 C'est vrai
			P1:Était X-> Y