800 lines
31 KiB
Python
800 lines
31 KiB
Python
# Natural Language Toolkit: Dependency Grammars
|
|
#
|
|
# Copyright (C) 2001-2023 NLTK Project
|
|
# Author: Jason Narad <jason.narad@gmail.com>
|
|
# Steven Bird <stevenbird1@gmail.com> (modifications)
|
|
#
|
|
# URL: <https://www.nltk.org/>
|
|
# For license information, see LICENSE.TXT
|
|
#
|
|
|
|
"""
|
|
Tools for reading and writing dependency trees.
|
|
The input is assumed to be in Malt-TAB format
|
|
(https://stp.lingfil.uu.se/~nivre/research/MaltXML.html).
|
|
"""
|
|
|
|
import subprocess
|
|
import warnings
|
|
from collections import defaultdict
|
|
from itertools import chain
|
|
from pprint import pformat
|
|
|
|
from nltk.internals import find_binary
|
|
from nltk.tree import Tree
|
|
|
|
#################################################################
|
|
# DependencyGraph Class
|
|
#################################################################
|
|
|
|
|
|
class DependencyGraph:
|
|
"""
|
|
A container for the nodes and labelled edges of a dependency structure.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
tree_str=None,
|
|
cell_extractor=None,
|
|
zero_based=False,
|
|
cell_separator=None,
|
|
top_relation_label="ROOT",
|
|
):
|
|
"""Dependency graph.
|
|
|
|
We place a dummy `TOP` node with the index 0, since the root node is
|
|
often assigned 0 as its head. This also means that the indexing of the
|
|
nodes corresponds directly to the Malt-TAB format, which starts at 1.
|
|
|
|
If zero-based is True, then Malt-TAB-like input with node numbers
|
|
starting at 0 and the root node assigned -1 (as produced by, e.g.,
|
|
zpar).
|
|
|
|
:param str cell_separator: the cell separator. If not provided, cells
|
|
are split by whitespace.
|
|
|
|
:param str top_relation_label: the label by which the top relation is
|
|
identified, for examlple, `ROOT`, `null` or `TOP`.
|
|
"""
|
|
self.nodes = defaultdict(
|
|
lambda: {
|
|
"address": None,
|
|
"word": None,
|
|
"lemma": None,
|
|
"ctag": None,
|
|
"tag": None,
|
|
"feats": None,
|
|
"head": None,
|
|
"deps": defaultdict(list),
|
|
"rel": None,
|
|
}
|
|
)
|
|
|
|
self.nodes[0].update({"ctag": "TOP", "tag": "TOP", "address": 0})
|
|
|
|
self.root = None
|
|
|
|
if tree_str:
|
|
self._parse(
|
|
tree_str,
|
|
cell_extractor=cell_extractor,
|
|
zero_based=zero_based,
|
|
cell_separator=cell_separator,
|
|
top_relation_label=top_relation_label,
|
|
)
|
|
|
|
def remove_by_address(self, address):
|
|
"""
|
|
Removes the node with the given address. References
|
|
to this node in others will still exist.
|
|
"""
|
|
del self.nodes[address]
|
|
|
|
def redirect_arcs(self, originals, redirect):
|
|
"""
|
|
Redirects arcs to any of the nodes in the originals list
|
|
to the redirect node address.
|
|
"""
|
|
for node in self.nodes.values():
|
|
new_deps = []
|
|
for dep in node["deps"]:
|
|
if dep in originals:
|
|
new_deps.append(redirect)
|
|
else:
|
|
new_deps.append(dep)
|
|
node["deps"] = new_deps
|
|
|
|
def add_arc(self, head_address, mod_address):
|
|
"""
|
|
Adds an arc from the node specified by head_address to the
|
|
node specified by the mod address.
|
|
"""
|
|
relation = self.nodes[mod_address]["rel"]
|
|
self.nodes[head_address]["deps"].setdefault(relation, [])
|
|
self.nodes[head_address]["deps"][relation].append(mod_address)
|
|
# self.nodes[head_address]['deps'].append(mod_address)
|
|
|
|
def connect_graph(self):
|
|
"""
|
|
Fully connects all non-root nodes. All nodes are set to be dependents
|
|
of the root node.
|
|
"""
|
|
for node1 in self.nodes.values():
|
|
for node2 in self.nodes.values():
|
|
if node1["address"] != node2["address"] and node2["rel"] != "TOP":
|
|
relation = node2["rel"]
|
|
node1["deps"].setdefault(relation, [])
|
|
node1["deps"][relation].append(node2["address"])
|
|
# node1['deps'].append(node2['address'])
|
|
|
|
def get_by_address(self, node_address):
|
|
"""Return the node with the given address."""
|
|
return self.nodes[node_address]
|
|
|
|
def contains_address(self, node_address):
|
|
"""
|
|
Returns true if the graph contains a node with the given node
|
|
address, false otherwise.
|
|
"""
|
|
return node_address in self.nodes
|
|
|
|
def to_dot(self):
|
|
"""Return a dot representation suitable for using with Graphviz.
|
|
|
|
>>> dg = DependencyGraph(
|
|
... 'John N 2\\n'
|
|
... 'loves V 0\\n'
|
|
... 'Mary N 2'
|
|
... )
|
|
>>> print(dg.to_dot())
|
|
digraph G{
|
|
edge [dir=forward]
|
|
node [shape=plaintext]
|
|
<BLANKLINE>
|
|
0 [label="0 (None)"]
|
|
0 -> 2 [label="ROOT"]
|
|
1 [label="1 (John)"]
|
|
2 [label="2 (loves)"]
|
|
2 -> 1 [label=""]
|
|
2 -> 3 [label=""]
|
|
3 [label="3 (Mary)"]
|
|
}
|
|
|
|
"""
|
|
# Start the digraph specification
|
|
s = "digraph G{\n"
|
|
s += "edge [dir=forward]\n"
|
|
s += "node [shape=plaintext]\n"
|
|
|
|
# Draw the remaining nodes
|
|
for node in sorted(self.nodes.values(), key=lambda v: v["address"]):
|
|
s += '\n{} [label="{} ({})"]'.format(
|
|
node["address"],
|
|
node["address"],
|
|
node["word"],
|
|
)
|
|
for rel, deps in node["deps"].items():
|
|
for dep in deps:
|
|
if rel is not None:
|
|
s += '\n{} -> {} [label="{}"]'.format(node["address"], dep, rel)
|
|
else:
|
|
s += "\n{} -> {} ".format(node["address"], dep)
|
|
s += "\n}"
|
|
|
|
return s
|
|
|
|
def _repr_svg_(self):
|
|
"""Show SVG representation of the transducer (IPython magic).
|
|
>>> from nltk.test.setup_fixt import check_binary
|
|
>>> check_binary('dot')
|
|
>>> dg = DependencyGraph(
|
|
... 'John N 2\\n'
|
|
... 'loves V 0\\n'
|
|
... 'Mary N 2'
|
|
... )
|
|
>>> dg._repr_svg_().split('\\n')[0]
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
|
|
|
|
"""
|
|
dot_string = self.to_dot()
|
|
return dot2img(dot_string)
|
|
|
|
def __str__(self):
|
|
return pformat(self.nodes)
|
|
|
|
def __repr__(self):
|
|
return f"<DependencyGraph with {len(self.nodes)} nodes>"
|
|
|
|
@staticmethod
|
|
def load(
|
|
filename, zero_based=False, cell_separator=None, top_relation_label="ROOT"
|
|
):
|
|
"""
|
|
:param filename: a name of a file in Malt-TAB format
|
|
:param zero_based: nodes in the input file are numbered starting from 0
|
|
rather than 1 (as produced by, e.g., zpar)
|
|
:param str cell_separator: the cell separator. If not provided, cells
|
|
are split by whitespace.
|
|
:param str top_relation_label: the label by which the top relation is
|
|
identified, for examlple, `ROOT`, `null` or `TOP`.
|
|
|
|
:return: a list of DependencyGraphs
|
|
|
|
"""
|
|
with open(filename) as infile:
|
|
return [
|
|
DependencyGraph(
|
|
tree_str,
|
|
zero_based=zero_based,
|
|
cell_separator=cell_separator,
|
|
top_relation_label=top_relation_label,
|
|
)
|
|
for tree_str in infile.read().split("\n\n")
|
|
]
|
|
|
|
def left_children(self, node_index):
|
|
"""
|
|
Returns the number of left children under the node specified
|
|
by the given address.
|
|
"""
|
|
children = chain.from_iterable(self.nodes[node_index]["deps"].values())
|
|
index = self.nodes[node_index]["address"]
|
|
return sum(1 for c in children if c < index)
|
|
|
|
def right_children(self, node_index):
|
|
"""
|
|
Returns the number of right children under the node specified
|
|
by the given address.
|
|
"""
|
|
children = chain.from_iterable(self.nodes[node_index]["deps"].values())
|
|
index = self.nodes[node_index]["address"]
|
|
return sum(1 for c in children if c > index)
|
|
|
|
def add_node(self, node):
|
|
if not self.contains_address(node["address"]):
|
|
self.nodes[node["address"]].update(node)
|
|
|
|
def _parse(
|
|
self,
|
|
input_,
|
|
cell_extractor=None,
|
|
zero_based=False,
|
|
cell_separator=None,
|
|
top_relation_label="ROOT",
|
|
):
|
|
"""Parse a sentence.
|
|
|
|
:param extractor: a function that given a tuple of cells returns a
|
|
7-tuple, where the values are ``word, lemma, ctag, tag, feats, head,
|
|
rel``.
|
|
|
|
:param str cell_separator: the cell separator. If not provided, cells
|
|
are split by whitespace.
|
|
|
|
:param str top_relation_label: the label by which the top relation is
|
|
identified, for examlple, `ROOT`, `null` or `TOP`.
|
|
|
|
"""
|
|
|
|
def extract_3_cells(cells, index):
|
|
word, tag, head = cells
|
|
return index, word, word, tag, tag, "", head, ""
|
|
|
|
def extract_4_cells(cells, index):
|
|
word, tag, head, rel = cells
|
|
return index, word, word, tag, tag, "", head, rel
|
|
|
|
def extract_7_cells(cells, index):
|
|
line_index, word, lemma, tag, _, head, rel = cells
|
|
try:
|
|
index = int(line_index)
|
|
except ValueError:
|
|
# index can't be parsed as an integer, use default
|
|
pass
|
|
return index, word, lemma, tag, tag, "", head, rel
|
|
|
|
def extract_10_cells(cells, index):
|
|
line_index, word, lemma, ctag, tag, feats, head, rel, _, _ = cells
|
|
try:
|
|
index = int(line_index)
|
|
except ValueError:
|
|
# index can't be parsed as an integer, use default
|
|
pass
|
|
return index, word, lemma, ctag, tag, feats, head, rel
|
|
|
|
extractors = {
|
|
3: extract_3_cells,
|
|
4: extract_4_cells,
|
|
7: extract_7_cells,
|
|
10: extract_10_cells,
|
|
}
|
|
|
|
if isinstance(input_, str):
|
|
input_ = (line for line in input_.split("\n"))
|
|
|
|
lines = (l.rstrip() for l in input_)
|
|
lines = (l for l in lines if l)
|
|
|
|
cell_number = None
|
|
for index, line in enumerate(lines, start=1):
|
|
cells = line.split(cell_separator)
|
|
if cell_number is None:
|
|
cell_number = len(cells)
|
|
else:
|
|
assert cell_number == len(cells)
|
|
|
|
if cell_extractor is None:
|
|
try:
|
|
cell_extractor = extractors[cell_number]
|
|
except KeyError as e:
|
|
raise ValueError(
|
|
"Number of tab-delimited fields ({}) not supported by "
|
|
"CoNLL(10) or Malt-Tab(4) format".format(cell_number)
|
|
) from e
|
|
|
|
try:
|
|
index, word, lemma, ctag, tag, feats, head, rel = cell_extractor(
|
|
cells, index
|
|
)
|
|
except (TypeError, ValueError):
|
|
# cell_extractor doesn't take 2 arguments or doesn't return 8
|
|
# values; assume the cell_extractor is an older external
|
|
# extractor and doesn't accept or return an index.
|
|
word, lemma, ctag, tag, feats, head, rel = cell_extractor(cells)
|
|
|
|
if head == "_":
|
|
continue
|
|
|
|
head = int(head)
|
|
if zero_based:
|
|
head += 1
|
|
|
|
self.nodes[index].update(
|
|
{
|
|
"address": index,
|
|
"word": word,
|
|
"lemma": lemma,
|
|
"ctag": ctag,
|
|
"tag": tag,
|
|
"feats": feats,
|
|
"head": head,
|
|
"rel": rel,
|
|
}
|
|
)
|
|
|
|
# Make sure that the fake root node has labeled dependencies.
|
|
if (cell_number == 3) and (head == 0):
|
|
rel = top_relation_label
|
|
self.nodes[head]["deps"][rel].append(index)
|
|
|
|
if self.nodes[0]["deps"][top_relation_label]:
|
|
root_address = self.nodes[0]["deps"][top_relation_label][0]
|
|
self.root = self.nodes[root_address]
|
|
self.top_relation_label = top_relation_label
|
|
else:
|
|
warnings.warn(
|
|
"The graph doesn't contain a node " "that depends on the root element."
|
|
)
|
|
|
|
def _word(self, node, filter=True):
|
|
w = node["word"]
|
|
if filter:
|
|
if w != ",":
|
|
return w
|
|
return w
|
|
|
|
def _tree(self, i):
|
|
"""Turn dependency graphs into NLTK trees.
|
|
|
|
:param int i: index of a node
|
|
:return: either a word (if the indexed node is a leaf) or a ``Tree``.
|
|
"""
|
|
node = self.get_by_address(i)
|
|
word = node["word"]
|
|
deps = sorted(chain.from_iterable(node["deps"].values()))
|
|
|
|
if deps:
|
|
return Tree(word, [self._tree(dep) for dep in deps])
|
|
else:
|
|
return word
|
|
|
|
def tree(self):
|
|
"""
|
|
Starting with the ``root`` node, build a dependency tree using the NLTK
|
|
``Tree`` constructor. Dependency labels are omitted.
|
|
"""
|
|
node = self.root
|
|
|
|
word = node["word"]
|
|
deps = sorted(chain.from_iterable(node["deps"].values()))
|
|
return Tree(word, [self._tree(dep) for dep in deps])
|
|
|
|
def triples(self, node=None):
|
|
"""
|
|
Extract dependency triples of the form:
|
|
((head word, head tag), rel, (dep word, dep tag))
|
|
"""
|
|
|
|
if not node:
|
|
node = self.root
|
|
|
|
head = (node["word"], node["ctag"])
|
|
for i in sorted(chain.from_iterable(node["deps"].values())):
|
|
dep = self.get_by_address(i)
|
|
yield (head, dep["rel"], (dep["word"], dep["ctag"]))
|
|
yield from self.triples(node=dep)
|
|
|
|
def _hd(self, i):
|
|
try:
|
|
return self.nodes[i]["head"]
|
|
except IndexError:
|
|
return None
|
|
|
|
def _rel(self, i):
|
|
try:
|
|
return self.nodes[i]["rel"]
|
|
except IndexError:
|
|
return None
|
|
|
|
# what's the return type? Boolean or list?
|
|
def contains_cycle(self):
|
|
"""Check whether there are cycles.
|
|
|
|
>>> dg = DependencyGraph(treebank_data)
|
|
>>> dg.contains_cycle()
|
|
False
|
|
|
|
>>> cyclic_dg = DependencyGraph()
|
|
>>> top = {'word': None, 'deps': [1], 'rel': 'TOP', 'address': 0}
|
|
>>> child1 = {'word': None, 'deps': [2], 'rel': 'NTOP', 'address': 1}
|
|
>>> child2 = {'word': None, 'deps': [4], 'rel': 'NTOP', 'address': 2}
|
|
>>> child3 = {'word': None, 'deps': [1], 'rel': 'NTOP', 'address': 3}
|
|
>>> child4 = {'word': None, 'deps': [3], 'rel': 'NTOP', 'address': 4}
|
|
>>> cyclic_dg.nodes = {
|
|
... 0: top,
|
|
... 1: child1,
|
|
... 2: child2,
|
|
... 3: child3,
|
|
... 4: child4,
|
|
... }
|
|
>>> cyclic_dg.root = top
|
|
|
|
>>> cyclic_dg.contains_cycle()
|
|
[1, 2, 4, 3]
|
|
|
|
"""
|
|
distances = {}
|
|
|
|
for node in self.nodes.values():
|
|
for dep in node["deps"]:
|
|
key = tuple([node["address"], dep])
|
|
distances[key] = 1
|
|
|
|
for _ in self.nodes:
|
|
new_entries = {}
|
|
|
|
for pair1 in distances:
|
|
for pair2 in distances:
|
|
if pair1[1] == pair2[0]:
|
|
key = tuple([pair1[0], pair2[1]])
|
|
new_entries[key] = distances[pair1] + distances[pair2]
|
|
|
|
for pair in new_entries:
|
|
distances[pair] = new_entries[pair]
|
|
if pair[0] == pair[1]:
|
|
path = self.get_cycle_path(self.get_by_address(pair[0]), pair[0])
|
|
return path
|
|
|
|
return False # return []?
|
|
|
|
def get_cycle_path(self, curr_node, goal_node_index):
|
|
for dep in curr_node["deps"]:
|
|
if dep == goal_node_index:
|
|
return [curr_node["address"]]
|
|
for dep in curr_node["deps"]:
|
|
path = self.get_cycle_path(self.get_by_address(dep), goal_node_index)
|
|
if len(path) > 0:
|
|
path.insert(0, curr_node["address"])
|
|
return path
|
|
return []
|
|
|
|
def to_conll(self, style):
|
|
"""
|
|
The dependency graph in CoNLL format.
|
|
|
|
:param style: the style to use for the format (3, 4, 10 columns)
|
|
:type style: int
|
|
:rtype: str
|
|
"""
|
|
|
|
if style == 3:
|
|
template = "{word}\t{tag}\t{head}\n"
|
|
elif style == 4:
|
|
template = "{word}\t{tag}\t{head}\t{rel}\n"
|
|
elif style == 10:
|
|
template = (
|
|
"{i}\t{word}\t{lemma}\t{ctag}\t{tag}\t{feats}\t{head}\t{rel}\t_\t_\n"
|
|
)
|
|
else:
|
|
raise ValueError(
|
|
"Number of tab-delimited fields ({}) not supported by "
|
|
"CoNLL(10) or Malt-Tab(4) format".format(style)
|
|
)
|
|
|
|
return "".join(
|
|
template.format(i=i, **node)
|
|
for i, node in sorted(self.nodes.items())
|
|
if node["tag"] != "TOP"
|
|
)
|
|
|
|
def nx_graph(self):
|
|
"""Convert the data in a ``nodelist`` into a networkx labeled directed graph."""
|
|
import networkx
|
|
|
|
nx_nodelist = list(range(1, len(self.nodes)))
|
|
nx_edgelist = [
|
|
(n, self._hd(n), self._rel(n)) for n in nx_nodelist if self._hd(n)
|
|
]
|
|
self.nx_labels = {}
|
|
for n in nx_nodelist:
|
|
self.nx_labels[n] = self.nodes[n]["word"]
|
|
|
|
g = networkx.MultiDiGraph()
|
|
g.add_nodes_from(nx_nodelist)
|
|
g.add_edges_from(nx_edgelist)
|
|
|
|
return g
|
|
|
|
|
|
def dot2img(dot_string, t="svg"):
|
|
"""
|
|
Create image representation fom dot_string, using the 'dot' program
|
|
from the Graphviz package.
|
|
|
|
Use the 't' argument to specify the image file format, for ex. 'jpeg', 'eps',
|
|
'json', 'png' or 'webp' (Running 'dot -T:' lists all available formats).
|
|
|
|
Note that the "capture_output" option of subprocess.run() is only available
|
|
with text formats (like svg), but not with binary image formats (like png).
|
|
"""
|
|
|
|
try:
|
|
find_binary("dot")
|
|
try:
|
|
if t in ["dot", "dot_json", "json", "svg"]:
|
|
proc = subprocess.run(
|
|
["dot", "-T%s" % t],
|
|
capture_output=True,
|
|
input=dot_string,
|
|
text=True,
|
|
)
|
|
else:
|
|
proc = subprocess.run(
|
|
["dot", "-T%s" % t],
|
|
input=bytes(dot_string, encoding="utf8"),
|
|
)
|
|
return proc.stdout
|
|
except:
|
|
raise Exception(
|
|
"Cannot create image representation by running dot from string: {}"
|
|
"".format(dot_string)
|
|
)
|
|
except OSError as e:
|
|
raise Exception("Cannot find the dot binary from Graphviz package") from e
|
|
|
|
|
|
class DependencyGraphError(Exception):
|
|
"""Dependency graph exception."""
|
|
|
|
|
|
def demo():
|
|
malt_demo()
|
|
conll_demo()
|
|
conll_file_demo()
|
|
cycle_finding_demo()
|
|
|
|
|
|
def malt_demo(nx=False):
|
|
"""
|
|
A demonstration of the result of reading a dependency
|
|
version of the first sentence of the Penn Treebank.
|
|
"""
|
|
dg = DependencyGraph(
|
|
"""Pierre NNP 2 NMOD
|
|
Vinken NNP 8 SUB
|
|
, , 2 P
|
|
61 CD 5 NMOD
|
|
years NNS 6 AMOD
|
|
old JJ 2 NMOD
|
|
, , 2 P
|
|
will MD 0 ROOT
|
|
join VB 8 VC
|
|
the DT 11 NMOD
|
|
board NN 9 OBJ
|
|
as IN 9 VMOD
|
|
a DT 15 NMOD
|
|
nonexecutive JJ 15 NMOD
|
|
director NN 12 PMOD
|
|
Nov. NNP 9 VMOD
|
|
29 CD 16 NMOD
|
|
. . 9 VMOD
|
|
"""
|
|
)
|
|
tree = dg.tree()
|
|
tree.pprint()
|
|
if nx:
|
|
# currently doesn't work
|
|
import networkx
|
|
from matplotlib import pylab
|
|
|
|
g = dg.nx_graph()
|
|
g.info()
|
|
pos = networkx.spring_layout(g, dim=1)
|
|
networkx.draw_networkx_nodes(g, pos, node_size=50)
|
|
# networkx.draw_networkx_edges(g, pos, edge_color='k', width=8)
|
|
networkx.draw_networkx_labels(g, pos, dg.nx_labels)
|
|
pylab.xticks([])
|
|
pylab.yticks([])
|
|
pylab.savefig("tree.png")
|
|
pylab.show()
|
|
|
|
|
|
def conll_demo():
|
|
"""
|
|
A demonstration of how to read a string representation of
|
|
a CoNLL format dependency tree.
|
|
"""
|
|
dg = DependencyGraph(conll_data1)
|
|
tree = dg.tree()
|
|
tree.pprint()
|
|
print(dg)
|
|
print(dg.to_conll(4))
|
|
|
|
|
|
def conll_file_demo():
|
|
print("Mass conll_read demo...")
|
|
graphs = [DependencyGraph(entry) for entry in conll_data2.split("\n\n") if entry]
|
|
for graph in graphs:
|
|
tree = graph.tree()
|
|
print("\n")
|
|
tree.pprint()
|
|
|
|
|
|
def cycle_finding_demo():
|
|
dg = DependencyGraph(treebank_data)
|
|
print(dg.contains_cycle())
|
|
cyclic_dg = DependencyGraph()
|
|
cyclic_dg.add_node({"word": None, "deps": [1], "rel": "TOP", "address": 0})
|
|
cyclic_dg.add_node({"word": None, "deps": [2], "rel": "NTOP", "address": 1})
|
|
cyclic_dg.add_node({"word": None, "deps": [4], "rel": "NTOP", "address": 2})
|
|
cyclic_dg.add_node({"word": None, "deps": [1], "rel": "NTOP", "address": 3})
|
|
cyclic_dg.add_node({"word": None, "deps": [3], "rel": "NTOP", "address": 4})
|
|
print(cyclic_dg.contains_cycle())
|
|
|
|
|
|
treebank_data = """Pierre NNP 2 NMOD
|
|
Vinken NNP 8 SUB
|
|
, , 2 P
|
|
61 CD 5 NMOD
|
|
years NNS 6 AMOD
|
|
old JJ 2 NMOD
|
|
, , 2 P
|
|
will MD 0 ROOT
|
|
join VB 8 VC
|
|
the DT 11 NMOD
|
|
board NN 9 OBJ
|
|
as IN 9 VMOD
|
|
a DT 15 NMOD
|
|
nonexecutive JJ 15 NMOD
|
|
director NN 12 PMOD
|
|
Nov. NNP 9 VMOD
|
|
29 CD 16 NMOD
|
|
. . 9 VMOD
|
|
"""
|
|
|
|
conll_data1 = """
|
|
1 Ze ze Pron Pron per|3|evofmv|nom 2 su _ _
|
|
2 had heb V V trans|ovt|1of2of3|ev 0 ROOT _ _
|
|
3 met met Prep Prep voor 8 mod _ _
|
|
4 haar haar Pron Pron bez|3|ev|neut|attr 5 det _ _
|
|
5 moeder moeder N N soort|ev|neut 3 obj1 _ _
|
|
6 kunnen kan V V hulp|ott|1of2of3|mv 2 vc _ _
|
|
7 gaan ga V V hulp|inf 6 vc _ _
|
|
8 winkelen winkel V V intrans|inf 11 cnj _ _
|
|
9 , , Punc Punc komma 8 punct _ _
|
|
10 zwemmen zwem V V intrans|inf 11 cnj _ _
|
|
11 of of Conj Conj neven 7 vc _ _
|
|
12 terrassen terras N N soort|mv|neut 11 cnj _ _
|
|
13 . . Punc Punc punt 12 punct _ _
|
|
"""
|
|
|
|
conll_data2 = """1 Cathy Cathy N N eigen|ev|neut 2 su _ _
|
|
2 zag zie V V trans|ovt|1of2of3|ev 0 ROOT _ _
|
|
3 hen hen Pron Pron per|3|mv|datofacc 2 obj1 _ _
|
|
4 wild wild Adj Adj attr|stell|onverv 5 mod _ _
|
|
5 zwaaien zwaai N N soort|mv|neut 2 vc _ _
|
|
6 . . Punc Punc punt 5 punct _ _
|
|
|
|
1 Ze ze Pron Pron per|3|evofmv|nom 2 su _ _
|
|
2 had heb V V trans|ovt|1of2of3|ev 0 ROOT _ _
|
|
3 met met Prep Prep voor 8 mod _ _
|
|
4 haar haar Pron Pron bez|3|ev|neut|attr 5 det _ _
|
|
5 moeder moeder N N soort|ev|neut 3 obj1 _ _
|
|
6 kunnen kan V V hulp|ott|1of2of3|mv 2 vc _ _
|
|
7 gaan ga V V hulp|inf 6 vc _ _
|
|
8 winkelen winkel V V intrans|inf 11 cnj _ _
|
|
9 , , Punc Punc komma 8 punct _ _
|
|
10 zwemmen zwem V V intrans|inf 11 cnj _ _
|
|
11 of of Conj Conj neven 7 vc _ _
|
|
12 terrassen terras N N soort|mv|neut 11 cnj _ _
|
|
13 . . Punc Punc punt 12 punct _ _
|
|
|
|
1 Dat dat Pron Pron aanw|neut|attr 2 det _ _
|
|
2 werkwoord werkwoord N N soort|ev|neut 6 obj1 _ _
|
|
3 had heb V V hulp|ovt|1of2of3|ev 0 ROOT _ _
|
|
4 ze ze Pron Pron per|3|evofmv|nom 6 su _ _
|
|
5 zelf zelf Pron Pron aanw|neut|attr|wzelf 3 predm _ _
|
|
6 uitgevonden vind V V trans|verldw|onverv 3 vc _ _
|
|
7 . . Punc Punc punt 6 punct _ _
|
|
|
|
1 Het het Pron Pron onbep|neut|zelfst 2 su _ _
|
|
2 hoorde hoor V V trans|ovt|1of2of3|ev 0 ROOT _ _
|
|
3 bij bij Prep Prep voor 2 ld _ _
|
|
4 de de Art Art bep|zijdofmv|neut 6 det _ _
|
|
5 warme warm Adj Adj attr|stell|vervneut 6 mod _ _
|
|
6 zomerdag zomerdag N N soort|ev|neut 3 obj1 _ _
|
|
7 die die Pron Pron betr|neut|zelfst 6 mod _ _
|
|
8 ze ze Pron Pron per|3|evofmv|nom 12 su _ _
|
|
9 ginds ginds Adv Adv gew|aanw 12 mod _ _
|
|
10 achter achter Adv Adv gew|geenfunc|stell|onverv 12 svp _ _
|
|
11 had heb V V hulp|ovt|1of2of3|ev 7 body _ _
|
|
12 gelaten laat V V trans|verldw|onverv 11 vc _ _
|
|
13 . . Punc Punc punt 12 punct _ _
|
|
|
|
1 Ze ze Pron Pron per|3|evofmv|nom 2 su _ _
|
|
2 hadden heb V V trans|ovt|1of2of3|mv 0 ROOT _ _
|
|
3 languit languit Adv Adv gew|geenfunc|stell|onverv 11 mod _ _
|
|
4 naast naast Prep Prep voor 11 mod _ _
|
|
5 elkaar elkaar Pron Pron rec|neut 4 obj1 _ _
|
|
6 op op Prep Prep voor 11 ld _ _
|
|
7 de de Art Art bep|zijdofmv|neut 8 det _ _
|
|
8 strandstoelen strandstoel N N soort|mv|neut 6 obj1 _ _
|
|
9 kunnen kan V V hulp|inf 2 vc _ _
|
|
10 gaan ga V V hulp|inf 9 vc _ _
|
|
11 liggen lig V V intrans|inf 10 vc _ _
|
|
12 . . Punc Punc punt 11 punct _ _
|
|
|
|
1 Zij zij Pron Pron per|3|evofmv|nom 2 su _ _
|
|
2 zou zal V V hulp|ovt|1of2of3|ev 7 cnj _ _
|
|
3 mams mams N N soort|ev|neut 4 det _ _
|
|
4 rug rug N N soort|ev|neut 5 obj1 _ _
|
|
5 ingewreven wrijf V V trans|verldw|onverv 6 vc _ _
|
|
6 hebben heb V V hulp|inf 2 vc _ _
|
|
7 en en Conj Conj neven 0 ROOT _ _
|
|
8 mam mam V V trans|ovt|1of2of3|ev 7 cnj _ _
|
|
9 de de Art Art bep|zijdofmv|neut 10 det _ _
|
|
10 hare hare Pron Pron bez|3|ev|neut|attr 8 obj1 _ _
|
|
11 . . Punc Punc punt 10 punct _ _
|
|
|
|
1 Of of Conj Conj onder|metfin 0 ROOT _ _
|
|
2 ze ze Pron Pron per|3|evofmv|nom 3 su _ _
|
|
3 had heb V V hulp|ovt|1of2of3|ev 0 ROOT _ _
|
|
4 gewoon gewoon Adj Adj adv|stell|onverv 10 mod _ _
|
|
5 met met Prep Prep voor 10 mod _ _
|
|
6 haar haar Pron Pron bez|3|ev|neut|attr 7 det _ _
|
|
7 vriendinnen vriendin N N soort|mv|neut 5 obj1 _ _
|
|
8 rond rond Adv Adv deelv 10 svp _ _
|
|
9 kunnen kan V V hulp|inf 3 vc _ _
|
|
10 slenteren slenter V V intrans|inf 9 vc _ _
|
|
11 in in Prep Prep voor 10 mod _ _
|
|
12 de de Art Art bep|zijdofmv|neut 13 det _ _
|
|
13 buurt buurt N N soort|ev|neut 11 obj1 _ _
|
|
14 van van Prep Prep voor 13 mod _ _
|
|
15 Trafalgar_Square Trafalgar_Square MWU N_N eigen|ev|neut_eigen|ev|neut 14 obj1 _ _
|
|
16 . . Punc Punc punt 15 punct _ _
|
|
"""
|
|
|
|
if __name__ == "__main__":
|
|
demo()
|