added tagging tools
This commit is contained in:
parent
bd7af6724b
commit
3fbf5fad68
1027
Tagging/ConlluGeneratorTest.ipynb
Normal file
1027
Tagging/ConlluGeneratorTest.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
186
Tagging/conllu_generator.py
Normal file
186
Tagging/conllu_generator.py
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import nltk
|
||||||
|
from nltk.tag import pos_tag, map_tag
|
||||||
|
from nltk.stem import PorterStemmer
|
||||||
|
from nltk.corpus import stopwords as nltk_stopwords
|
||||||
|
from stemmed_mwe_tokenizer import StemmedMWETokenizer
|
||||||
|
|
||||||
|
|
||||||
|
CONLLU_ATTRIBUTES = [
|
||||||
|
"id",
|
||||||
|
"form",
|
||||||
|
"lemma",
|
||||||
|
"upos",
|
||||||
|
"xpos",
|
||||||
|
"feats",
|
||||||
|
"head",
|
||||||
|
"deprel",
|
||||||
|
"deps",
|
||||||
|
"misc"
|
||||||
|
]
|
||||||
|
|
||||||
|
# took from: https://stackoverflow.com/a/16053211
|
||||||
|
|
||||||
|
|
||||||
|
def replace_tab(s, tabstop=4):
|
||||||
|
result = str()
|
||||||
|
for c in s:
|
||||||
|
if c == '\t':
|
||||||
|
while (len(result) % tabstop != 0):
|
||||||
|
result += ' '
|
||||||
|
else:
|
||||||
|
result += c
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ConlluDict(dict):
|
||||||
|
|
||||||
|
def from_str(self, s: str):
|
||||||
|
entries = s.split("|")
|
||||||
|
for entry in entries:
|
||||||
|
key, val = entry.split("=")
|
||||||
|
self[key.strip()] = val.strip()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if len(self) == 0:
|
||||||
|
return "_"
|
||||||
|
|
||||||
|
result = ""
|
||||||
|
for key, value in self.items():
|
||||||
|
result += key + "=" + value + "|"
|
||||||
|
|
||||||
|
return result[:-1]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
|
class ConlluElement(object):
|
||||||
|
# class uses format described here: https://universaldependencies.org/format.html
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: int,
|
||||||
|
form: str,
|
||||||
|
lemma: str,
|
||||||
|
upos: str = "_",
|
||||||
|
xpos: str = "_",
|
||||||
|
feats: str = "_",
|
||||||
|
head: str = "_",
|
||||||
|
deprel: str = "_",
|
||||||
|
deps: str = "_",
|
||||||
|
misc: str = "_"):
|
||||||
|
self.id = id
|
||||||
|
self.form = form
|
||||||
|
self.lemma = lemma
|
||||||
|
self.upos = upos
|
||||||
|
self.xpos = xpos
|
||||||
|
|
||||||
|
self.feats = ConlluDict()
|
||||||
|
if feats != "_":
|
||||||
|
self.feats.from_str(feats)
|
||||||
|
|
||||||
|
self.head = head
|
||||||
|
self.deprel = deprel
|
||||||
|
self.deps = deps
|
||||||
|
|
||||||
|
self.misc = ConlluDict()
|
||||||
|
if misc != "_":
|
||||||
|
self.misc.from_str(misc)
|
||||||
|
|
||||||
|
def add_feature(self, key: str, value: str):
|
||||||
|
self.feats[key] = value
|
||||||
|
|
||||||
|
def add_misc(self, key: str, value: str):
|
||||||
|
self.misc[key] = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
result = ""
|
||||||
|
for attr in CONLLU_ATTRIBUTES:
|
||||||
|
result += str(self.__getattribute__(attr)) + " \t"
|
||||||
|
return replace_tab(result, 16)
|
||||||
|
|
||||||
|
|
||||||
|
class ConlluDocument(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.conllu_elements = []
|
||||||
|
|
||||||
|
def add(self, conllu_element: ConlluElement):
|
||||||
|
self.conllu_elements.append(conllu_element)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
result = ""
|
||||||
|
for elem in self.conllu_elements:
|
||||||
|
result += elem.__repr__() + "\n"
|
||||||
|
|
||||||
|
return result + "\n"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
|
class ConlluGenerator(object):
|
||||||
|
def __init__(self, documents: list, stemmed_multi_word_tokens, stemmer=PorterStemmer()):
|
||||||
|
self.documents = documents
|
||||||
|
self.stemmed_multi_word_tokens = stemmed_multi_word_tokens
|
||||||
|
self.mwe_tokenizer = StemmedMWETokenizer(
|
||||||
|
[w.split() for w in stemmed_multi_word_tokens])
|
||||||
|
self.stemmer = stemmer
|
||||||
|
|
||||||
|
self.id_counter = 0
|
||||||
|
|
||||||
|
self.conllu_documents = []
|
||||||
|
|
||||||
|
def tokenize_and_stem(self):
|
||||||
|
tokenized_documents = []
|
||||||
|
|
||||||
|
for doc in self.documents:
|
||||||
|
simple_tokenized = nltk.tokenize.word_tokenize(doc)
|
||||||
|
tokenized_documents.append(
|
||||||
|
self.mwe_tokenizer.tokenize(simple_tokenized))
|
||||||
|
|
||||||
|
# now create initial colln-u elemnts
|
||||||
|
for doc in tokenized_documents:
|
||||||
|
self.id_counter = 0
|
||||||
|
collnu_doc = ConlluDocument()
|
||||||
|
for token in doc:
|
||||||
|
stemmed_token = None
|
||||||
|
if "_" in token:
|
||||||
|
stemmed_token = "_".join(
|
||||||
|
[self.stemmer.stem(part) for part in token.split("_")])
|
||||||
|
else:
|
||||||
|
stemmed_token = self.stemmer.stem(token)
|
||||||
|
collnu_doc.add(ConlluElement(
|
||||||
|
id=self.id_counter,
|
||||||
|
form=token,
|
||||||
|
lemma=stemmed_token
|
||||||
|
))
|
||||||
|
self.id_counter += 1
|
||||||
|
self.conllu_documents.append(collnu_doc)
|
||||||
|
|
||||||
|
def pos_tagging(self):
|
||||||
|
for conllu_document in self.conllu_documents:
|
||||||
|
tokens = [x.form for x in conllu_document.conllu_elements]
|
||||||
|
pos_tags = pos_tag(tokens)
|
||||||
|
simplified_tags = [map_tag('en-ptb', 'universal', tag)
|
||||||
|
for word, tag in pos_tags]
|
||||||
|
|
||||||
|
for i in range(len(tokens)):
|
||||||
|
conllu_elem = conllu_document.conllu_elements[i]
|
||||||
|
conllu_elem.upos = simplified_tags[i]
|
||||||
|
conllu_elem.xpos = pos_tags[i][1]
|
||||||
|
|
||||||
|
def add_misc_value_by_list(self, key, value, stemmed_keyword_list):
|
||||||
|
for conllu_document in self.conllu_documents:
|
||||||
|
for elem in conllu_document.conllu_elements:
|
||||||
|
if elem.lemma in stemmed_keyword_list:
|
||||||
|
elem.add_misc(key, value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
result = ""
|
||||||
|
for document in self.conllu_documents:
|
||||||
|
result += document.__repr__() + "\n"
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__repr__()
|
76
Tagging/recipe_collnu_generator.py
Executable file
76
Tagging/recipe_collnu_generator.py
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '..')
|
||||||
|
|
||||||
|
from conllu_generator import ConlluDict, ConlluElement, ConlluDocument, ConlluGenerator
|
||||||
|
import settings
|
||||||
|
import importlib.util
|
||||||
|
from json_buffered_reader import JSON_buffered_reader as JSON_br
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# loading ingredients:
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"ingredients", "../" + settings.ingredients_file)
|
||||||
|
ingredients = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(ingredients)
|
||||||
|
|
||||||
|
# load json reader
|
||||||
|
|
||||||
|
|
||||||
|
# settings:
|
||||||
|
recipe_buffer_size = 1000
|
||||||
|
recipe_buffers_per_file = 5
|
||||||
|
|
||||||
|
|
||||||
|
# create reader
|
||||||
|
buffered_reader_1M = JSON_br("../" + settings.one_million_recipes_file)
|
||||||
|
|
||||||
|
# open savefile:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def process_instructions(instructions: list):
|
||||||
|
|
||||||
|
if len(instructions) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
conllu_input_docs = [doc.replace("\n", " ")[:-1] for doc in instructions]
|
||||||
|
|
||||||
|
cg = ConlluGenerator(
|
||||||
|
conllu_input_docs, ingredients.multi_word_ingredients_stemmed)
|
||||||
|
cg.tokenize_and_stem()
|
||||||
|
cg.pos_tagging()
|
||||||
|
cg.add_misc_value_by_list("food_type", "ingredient", [w.replace(" ","_") for w in ingredients.multi_word_ingredients_stemmed] + ingredients.ingredients_stemmed)
|
||||||
|
|
||||||
|
savefile.write(str(cg))
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
buffer_count = 0
|
||||||
|
file_count = 0
|
||||||
|
|
||||||
|
savefile = open(f"recipes{file_count}.conllu", 'w')
|
||||||
|
instructions = []
|
||||||
|
|
||||||
|
for raw_recipe in buffered_reader_1M:
|
||||||
|
instruction = ""
|
||||||
|
for item in raw_recipe['instructions']:
|
||||||
|
instruction += item['text'] + '\n'
|
||||||
|
|
||||||
|
instructions.append(instruction)
|
||||||
|
i += 1
|
||||||
|
if i % recipe_buffer_size == 0:
|
||||||
|
process_instructions(instructions)
|
||||||
|
print(f"processed {i} recipes")
|
||||||
|
instructions = []
|
||||||
|
buffer_count += 1
|
||||||
|
if buffer_count % recipe_buffers_per_file == 0:
|
||||||
|
savefile.close()
|
||||||
|
savefile = open(f"recipes{file_count}.conllu", 'w')
|
||||||
|
file_count += 1
|
||||||
|
|
||||||
|
process_instructions(instructions)
|
||||||
|
print(f"processed {i} recipes")
|
||||||
|
|
||||||
|
savefile.close()
|
58
Tagging/stemmed_mwe_tokenizer.py
Normal file
58
Tagging/stemmed_mwe_tokenizer.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import nltk
|
||||||
|
from nltk import PorterStemmer
|
||||||
|
|
||||||
|
from nltk.util import Trie
|
||||||
|
|
||||||
|
# modified MWE Tokenizer which stems multi word expressions before the merge check
|
||||||
|
|
||||||
|
|
||||||
|
class StemmedMWETokenizer(nltk.tokenize.api.TokenizerI):
|
||||||
|
def __init__(self, stemmed_tokens, stemmer=PorterStemmer(), separator="_"):
|
||||||
|
self.stemmer = stemmer
|
||||||
|
self.stemmed_tokens = stemmed_tokens
|
||||||
|
self.mwes = Trie(stemmed_tokens)
|
||||||
|
self.separator = separator
|
||||||
|
|
||||||
|
def tokenize(self, text):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param text: A list containing tokenized text
|
||||||
|
:type text: list(str)
|
||||||
|
:return: A list of the tokenized text with multi-words merged together
|
||||||
|
:rtype: list(str)
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
|
||||||
|
>>> tokenizer = MWETokenizer([('hors', "d'oeuvre")], separator='+')
|
||||||
|
>>> tokenizer.tokenize("An hors d'oeuvre tonight, sir?".split())
|
||||||
|
['An', "hors+d'oeuvre", 'tonight,', 'sir?']
|
||||||
|
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
n = len(text)
|
||||||
|
result = []
|
||||||
|
|
||||||
|
while i < n:
|
||||||
|
if self.stemmer.stem(text[i]) in self.mwes:
|
||||||
|
# possible MWE match
|
||||||
|
j = i
|
||||||
|
trie = self.mwes
|
||||||
|
while j < n and self.stemmer.stem(text[j]) in trie:
|
||||||
|
trie = trie[self.stemmer.stem(text[j])]
|
||||||
|
j = j + 1
|
||||||
|
else:
|
||||||
|
if Trie.LEAF in trie:
|
||||||
|
# success!
|
||||||
|
result.append(self.separator.join(text[i:j]))
|
||||||
|
i = j
|
||||||
|
else:
|
||||||
|
# no match, so backtrack
|
||||||
|
result.append(text[i])
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
result.append(text[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return result
|
Loading…
Reference in New Issue
Block a user