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 est
NOUN_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 clauses
chunks` 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