Combinaison D'un Tokenizer en une grammaire et D'un Parser avec NLTK
je me fraie un chemin à travers le livre NLTK et je ne peux pas sembler faire quelque chose qui semblerait être une première étape naturelle pour construire une grammaire décente.
Mon objectif est de construire une grammaire pour un corpus de textes.
(question initiale: devrais-je essayer de commencer une grammaire à partir de zéro ou devrais-je commencer par une grammaire prédéfinie? Si je devais commencer par une autre grammaire, Quelle est la bonne pour commencer en anglais?)
Supposons que J'ai le texte suivant une grammaire simple:
simple_grammar = nltk.parse_cfg("""
S -> NP VP
PP -> P NP
NP -> Det N | Det N PP
VP -> V NP | VP PP
Det -> 'a' | 'A'
N -> 'car' | 'door'
V -> 'has'
P -> 'in' | 'for'
""");
cette grammaire peut analyser une phrase très simple, comme:
parser = nltk.ChartParser(simple_grammar)
trees = parser.nbest_parse("A car has a door")
maintenant je veux étendre cette grammaire pour traiter les phrases avec d'autres noms et verbes. Comment puis-je ajouter ces noms et verbes à ma grammaire sans les définir manuellement dans la grammaire?
par exemple, supposons que je veuille pouvoir analyser la phrase "une voiture a des roues". Je sais que les tokenizers fournis peuvent magie figure quels mots sont des verbes/noms, etc. Comment puis-je utiliser la sortie du tokenizer pour dire à la grammaire que "wheels" est un nom?
3 réponses
Vous pouvez exécuter un tagger POS sur votre texte et ensuite adapter votre grammaire pour travailler sur les tags POS Au lieu de mots.
> text = nltk.word_tokenize("A car has a door")
['A', 'car', 'has', 'a', 'door']
> tagged_text = nltk.pos_tag(text)
[('A', 'DT'), ('car', 'NN'), ('has', 'VBZ'), ('a', 'DT'), ('door', 'NN')]
> pos_tags = [pos for (token,pos) in nltk.pos_tag(text)]
['DT', 'NN', 'VBZ', 'DT', 'NN']
> simple_grammar = nltk.parse_cfg("""
S -> NP VP
PP -> P NP
NP -> Det N | Det N PP
VP -> V NP | VP PP
Det -> 'DT'
N -> 'NN'
V -> 'VBZ'
P -> 'PP'
""")
> parser = nltk.ChartParser(simple_grammar)
> tree = parser.parse(pos_tags)
Analyse est un problème délicat, beaucoup de choses peuvent mal se passer!
vous voulez (au moins) trois composants ici, un tokenizer, un tagger et enfin l'analyseur.
vous devez d'abord tokeniser le texte courant dans une liste de tokens. Cela peut être aussi simple que de diviser la chaîne de saisie autour de l'espace blanc, mais si vous analysez du texte plus général, vous aurez aussi besoin de gérer les nombres et la ponctuation, ce qui est non trivial. Par exemple la phrase de fin ne sont souvent pas considéré comme faisant partie du mot auquel il est attaché, mais les périodes marquant une abréviation sont souvent.
quand vous avez une liste de tokens d'entrée, vous pouvez utiliser un tagger pour essayer de déterminer la position de chaque mot, et l'utiliser pour désambiguer les séquences de tags d'entrée. Cela présente deux avantages principaux: D'abord, cela accélère l'analyse, car nous n'avons plus à envisager d'autres hypothèses autorisées par des mots ambigus, comme le POS-tagger l'a déjà fait. Deuxièmement, il améliore le traitement des mots inconnus, c'est-à-dire: mots pas dans votre grammaire, en assignant aussi à ces mots une étiquette (avec un peu de chance la bonne). Combinant un analyseur et d'un tagueur de cette façon est connu.
les POS-tags constitueront alors les pré-terminaux dans votre grammaire, les pré-terminaux sont les côtés gauche des productions avec seulement les terminaux comme leur côté droit. Ie en N -> "maison", V -> "sauter" etc. N et V sont préterminaux. Il est assez commun d'avoir la grammaire syntaxique, seulement les non-terminaux des deux côtés, les productions et lexical productions, un non-terminal allant à un terminal. Cela a un sens linguistique la plupart du temps, et la plupart des analyseurs CFG exigent que la grammaire soit sous cette forme. Toutefois, on pourrait représenter n'importe quel CFG de cette façon en créant des "productions fictives" à partir de n'importe quel terminal dans RHSes avec des non-terminaux dans eux.
il pourrait être nécessaire d'avoir une sorte de mappage entre les étiquettes POS et les pré-terminaux si vous voulez faire plus (ou moins) de distinctions d'étiquettes à grain fin dans votre grammaire que quoi votre tagger sorties. Vous pouvez ensuite initialiser le graphique avec les résultats du tagger, c'est à dire. éléments passifs de la catégorie appropriée couvrant chaque jeton d'entrée. Malheureusement, je ne sais pas NTLK, mais je suis sûr qu'il ya une façon simple de le faire. Lorsque la carte est ensemencée, l'analyse peut se poursuivre comme d'habitude, et n'importe quel arbre généalogique peut être extrait (y compris les mots) de façon régulière.
Toutefois, dans la plupart des applications pratiques, vous trouverez que l'analyseur peut retourner plusieurs l'analyse de la langue naturelle est très ambigu. Je ne sais pas quel type de corpus de texte vous tentez d'analyser, mais si c'est quelque chose comme le langage naturel, vous aurez probablement à construire une sorte de parse-modèle de sélection,cela nécessitera une branche d'arbre, une collection d'arbres de parse-taille allant de quelques centaines de parses à plusieurs milliers, tout dépend de votre grammaire et de la précision des résultats dont vous avez besoin. Compte tenu de cette treebank on peut inférer automagiquement un PCFG correspondant à il. Le PCFG peut alors être utilisé comme un modèle simple pour le classement des arbres parse.
Tout cela représente beaucoup de travail à faire vous-même. Pour quoi utilisez-vous les résultats de l'analyse? Avez-vous regardé d'autres ressources dans le NTLK ou d'autres paquets tels que le StanfordParser ou le BerkeleyParser?
je sais que c'est un an plus tard, mais je voulais ajouter quelques réflexions.
je prends beaucoup de phrases différentes et je les Étiquette avec des parties de discours pour un projet sur lequel je travaille. De là, J'ai fait comme StompChicken suggéré, en tirant les étiquettes des tuples (mot, étiquette) et en utilisant ces étiquettes comme les "terminaux" (les noeuds inférieurs de l'arbre que nous créons une phrase complètement étiquetée).
en fin de compte, cela ne suit pas mon désir de marquer des noms en tête dans les phrases de noms, puisque je ne peut pas tirer le nom de tête " mot " dans la grammaire, puisque la grammaire n'a que les étiquettes.
donc ce que j'ai fait était d'utiliser l'ensemble de tuples (mot, étiquette) pour créer un dictionnaire de balises, avec tous les mots avec cette étiquette comme valeurs pour cette étiquette. Puis j'imprime ce dictionnaire à l'écran/grammaire.cfg (context free grammar) file.
la forme que j'utilise pour l'imprimer fonctionne parfaitement avec la mise en place d'un analyseur en chargeant un fichier grammatical (analyseur = nltk.load_parser('grammaire.cfg'). L'une des lignes qu'il génère ressemble à ceci: VBG -> "escrime" | "bonging" | "montant" | "vivant" ... plus de 30 plus de mots...
donc maintenant ma grammaire a les mots réels comme terminaux et assigne les mêmes étiquettes que nltk.tag_pos.
J'espère que cela aidera n'importe qui d'autre voulant automatiser le marquage d'un grand corpus et avoir toujours les mots réels comme terminaux dans leur grammaire.
import nltk
from collections import defaultdict
tag_dict = defaultdict(list)
...
""" (Looping through sentences) """
# Tag
tagged_sent = nltk.pos_tag(tokens)
# Put tags and words into the dictionary
for word, tag in tagged_sent:
if tag not in tag_dict:
tag_dict[tag].append(word)
elif word not in tag_dict.get(tag):
tag_dict[tag].append(word)
# Printing to screen
for tag, words in tag_dict.items():
print tag, "->",
first_word = True
for word in words:
if first_word:
print "\"" + word + "\"",
first_word = False
else:
print "| \"" + word + "\"",
print ''