2014-07-07 05:36:43 +00:00
|
|
|
# cython: profile=True
|
2014-08-20 11:39:39 +00:00
|
|
|
# cython: embedsignature=True
|
|
|
|
"""Common classes and utilities across languages.
|
|
|
|
|
|
|
|
Provides the main implementation for the spacy tokenizer. Specific languages
|
|
|
|
subclass the Language class, over-writing the tokenization rules as necessary.
|
|
|
|
Special-case tokenization rules are read from data/<lang>/tokenization .
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2014-07-05 18:51:42 +00:00
|
|
|
from __future__ import unicode_literals
|
2014-07-07 02:21:06 +00:00
|
|
|
|
2014-07-07 06:05:29 +00:00
|
|
|
from libc.stdlib cimport calloc, free
|
2014-07-31 17:19:38 +00:00
|
|
|
from libcpp.pair cimport pair
|
|
|
|
from cython.operator cimport dereference as deref
|
2014-07-07 06:05:29 +00:00
|
|
|
|
2014-07-07 02:21:06 +00:00
|
|
|
from . import util
|
2014-07-07 05:36:43 +00:00
|
|
|
from os import path
|
2014-08-16 17:59:38 +00:00
|
|
|
|
2014-08-19 02:21:20 +00:00
|
|
|
TAGS = {}
|
2014-08-20 11:39:39 +00:00
|
|
|
DIST_FLAGS = {}
|
2014-07-07 14:58:48 +00:00
|
|
|
|
2014-07-07 10:47:21 +00:00
|
|
|
cdef class Language:
|
2014-08-21 21:49:14 +00:00
|
|
|
view_funcs = []
|
2014-07-07 10:47:21 +00:00
|
|
|
def __cinit__(self, name):
|
|
|
|
self.name = name
|
|
|
|
self.bacov = {}
|
2014-08-22 15:13:09 +00:00
|
|
|
self.chunks = {}
|
|
|
|
self.vocab = {}
|
2014-07-07 10:47:21 +00:00
|
|
|
self.load_tokenization(util.read_tokenization(name))
|
2014-08-20 11:39:39 +00:00
|
|
|
self.load_dist_info(util.read_dist_info(name))
|
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
cpdef list tokenize(self, unicode string):
|
2014-08-20 11:39:39 +00:00
|
|
|
"""Tokenize.
|
|
|
|
|
|
|
|
Split the string into tokens.
|
2014-07-07 10:47:21 +00:00
|
|
|
|
2014-08-20 11:39:39 +00:00
|
|
|
Args:
|
|
|
|
string (unicode): The string to split.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
tokens (Tokens): A Tokens object.
|
|
|
|
"""
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef list chunk
|
|
|
|
cdef list tokens = []
|
2014-08-18 18:23:54 +00:00
|
|
|
cdef size_t length = len(string)
|
2014-08-18 17:59:59 +00:00
|
|
|
cdef size_t start = 0
|
2014-08-18 18:48:48 +00:00
|
|
|
cdef size_t i = 0
|
|
|
|
for c in string:
|
2014-08-18 17:59:59 +00:00
|
|
|
if _is_whitespace(c):
|
|
|
|
if start < i:
|
2014-08-18 18:48:48 +00:00
|
|
|
chunk = self.lookup_chunk(string[start:i])
|
2014-08-22 15:28:23 +00:00
|
|
|
tokens.extend(chunk)
|
2014-08-18 17:59:59 +00:00
|
|
|
start = i + 1
|
2014-08-18 18:48:48 +00:00
|
|
|
i += 1
|
2014-08-18 17:59:59 +00:00
|
|
|
if start < i:
|
2014-08-18 18:48:48 +00:00
|
|
|
chunk = self.lookup_chunk(string[start:])
|
2014-08-22 15:28:23 +00:00
|
|
|
tokens.extend(chunk)
|
2014-07-07 10:47:21 +00:00
|
|
|
return tokens
|
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef Word lookup(self, unicode string):
|
2014-08-20 11:39:39 +00:00
|
|
|
assert len(string) != 0
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef Word word
|
2014-08-22 15:13:09 +00:00
|
|
|
cdef StringHash h = hash(string)
|
|
|
|
if h in self.vocab:
|
2014-08-22 15:28:23 +00:00
|
|
|
word = self.vocab[h]
|
2014-08-22 15:13:09 +00:00
|
|
|
else:
|
2014-08-18 17:14:00 +00:00
|
|
|
word = self.new_lexeme(string)
|
|
|
|
return word
|
2014-07-07 14:58:48 +00:00
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef list lookup_chunk(self, unicode string):
|
2014-08-18 18:48:48 +00:00
|
|
|
cdef StringHash h = hash(string)
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef list chunk
|
2014-08-22 15:13:09 +00:00
|
|
|
cdef size_t chunk_id
|
|
|
|
if h in self.chunks:
|
2014-08-22 15:28:23 +00:00
|
|
|
chunk = self.chunks[h]
|
2014-08-22 15:13:09 +00:00
|
|
|
else:
|
2014-08-18 18:48:48 +00:00
|
|
|
chunk = self.new_chunk(string, self.find_substrings(string))
|
2014-08-18 17:14:00 +00:00
|
|
|
return chunk
|
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef list new_chunk(self, unicode string, list substrings):
|
|
|
|
chunk = []
|
2014-08-18 17:14:00 +00:00
|
|
|
for i, substring in enumerate(substrings):
|
2014-08-22 15:28:23 +00:00
|
|
|
chunk.append(self.lookup(substring))
|
2014-08-22 15:13:09 +00:00
|
|
|
cdef StringHash h = hash(string)
|
2014-08-22 15:28:23 +00:00
|
|
|
self.chunks[h] = chunk
|
2014-08-18 17:14:00 +00:00
|
|
|
return chunk
|
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef Word new_lexeme(self, unicode string):
|
|
|
|
string_views = [view_func(string) for view_func in self.view_funcs]
|
|
|
|
word = Word(string.encode('utf8'), string_views)
|
2014-08-16 14:09:24 +00:00
|
|
|
self.bacov[word.lex] = string
|
2014-08-22 15:28:23 +00:00
|
|
|
self.vocab[word.lex] = word
|
2014-08-16 18:10:22 +00:00
|
|
|
return word
|
2014-08-16 01:22:03 +00:00
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
"""
|
2014-08-21 21:49:14 +00:00
|
|
|
def add_view_funcs(self, list view_funcs):
|
|
|
|
self.view_funcs.extend(view_funcs)
|
|
|
|
cdef size_t nr_views = len(self.view_funcs)
|
|
|
|
|
|
|
|
cdef unicode view
|
|
|
|
cdef StringHash hashed
|
|
|
|
cdef StringHash key
|
|
|
|
cdef unicode string
|
|
|
|
cdef LexID lex_id
|
|
|
|
cdef Lexeme* word
|
|
|
|
|
2014-08-22 15:13:09 +00:00
|
|
|
for key, lex_id in self.vocab.items():
|
2014-08-21 21:49:14 +00:00
|
|
|
word = <Lexeme*>lex_id
|
|
|
|
free(word.string_views)
|
|
|
|
word.string_views = <StringHash*>calloc(nr_views, sizeof(StringHash))
|
|
|
|
string = word.string[:word.length].decode('utf8')
|
|
|
|
for i, view_func in enumerate(self.view_funcs):
|
|
|
|
view = view_func(string)
|
|
|
|
hashed = hash(view)
|
|
|
|
word.string_views[i] = hashed
|
|
|
|
self.bacov[hashed] = view
|
2014-08-22 15:28:23 +00:00
|
|
|
"""
|
2014-08-21 21:49:14 +00:00
|
|
|
|
2014-08-20 11:39:39 +00:00
|
|
|
cpdef unicode unhash(self, StringHash hash_value):
|
2014-08-16 01:22:03 +00:00
|
|
|
'''Fetch a string from the reverse index, given its hash value.'''
|
2014-08-16 12:35:34 +00:00
|
|
|
return self.bacov[hash_value]
|
2014-08-16 01:22:03 +00:00
|
|
|
|
2014-08-20 11:39:39 +00:00
|
|
|
cpdef list find_substrings(self, unicode chunk):
|
|
|
|
"""Find how to split a chunk into substrings.
|
|
|
|
|
|
|
|
This method calls find_split repeatedly. Most languages will want to
|
|
|
|
override find_split, but it may be useful to override this instead.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
chunk (unicode): The string to be split, e.g. u"Mike's!"
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
substrings (list): The component substrings, e.g. [u"Mike", "'s", "!"].
|
|
|
|
"""
|
2014-08-18 17:14:00 +00:00
|
|
|
substrings = []
|
2014-08-20 11:39:39 +00:00
|
|
|
while chunk:
|
|
|
|
split = self.find_split(chunk)
|
2014-08-18 17:14:00 +00:00
|
|
|
if split == 0:
|
2014-08-20 11:39:39 +00:00
|
|
|
substrings.append(chunk)
|
2014-08-18 17:14:00 +00:00
|
|
|
break
|
2014-08-20 11:39:39 +00:00
|
|
|
substrings.append(chunk[:split])
|
|
|
|
chunk = chunk[split:]
|
2014-08-18 17:14:00 +00:00
|
|
|
return substrings
|
|
|
|
|
|
|
|
cdef int find_split(self, unicode word):
|
|
|
|
return len(word)
|
2014-08-16 01:22:03 +00:00
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef int set_orth(self, unicode string, Word word):
|
2014-08-20 11:39:39 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
def load_tokenization(self, token_rules):
|
|
|
|
'''Load special-case tokenization rules.
|
|
|
|
|
|
|
|
Loads special-case tokenization rules into the Language.chunk cache,
|
|
|
|
read from data/<lang>/tokenization . The special cases are loaded before
|
|
|
|
any language data is tokenized, giving these priority. For instance,
|
|
|
|
the English tokenization rules map "ain't" to ["are", "not"].
|
|
|
|
|
|
|
|
Args:
|
|
|
|
token_rules (list): A list of (chunk, tokens) pairs, where chunk is
|
|
|
|
a string and tokens is a list of strings.
|
|
|
|
'''
|
2014-08-18 17:14:00 +00:00
|
|
|
for chunk, tokens in token_rules:
|
2014-08-18 18:48:48 +00:00
|
|
|
self.new_chunk(chunk, tokens)
|
2014-08-18 17:59:59 +00:00
|
|
|
|
2014-08-19 00:40:37 +00:00
|
|
|
def load_dist_info(self, dist_info):
|
2014-08-20 11:39:39 +00:00
|
|
|
'''Load distributional information for the known lexemes of the language.
|
|
|
|
|
|
|
|
The distributional information is read from data/<lang>/dist_info.json .
|
|
|
|
It contains information like the (smoothed) unigram log probability of
|
|
|
|
the word, how often the word is found upper-cased, how often the word
|
|
|
|
is found title-cased, etc.
|
|
|
|
'''
|
2014-08-19 00:40:37 +00:00
|
|
|
cdef unicode string
|
|
|
|
cdef dict word_dist
|
2014-08-22 15:28:23 +00:00
|
|
|
cdef Word w
|
2014-08-19 00:40:37 +00:00
|
|
|
for string, word_dist in dist_info.items():
|
|
|
|
w = self.lookup(string)
|
2014-08-20 11:39:39 +00:00
|
|
|
w.prob = word_dist.prob
|
|
|
|
w.cluster = word_dist.cluster
|
2014-08-19 00:40:37 +00:00
|
|
|
for flag in word_dist.flags:
|
2014-08-20 11:39:39 +00:00
|
|
|
w.dist_flags |= DIST_FLAGS[flag]
|
2014-08-19 00:40:37 +00:00
|
|
|
for tag in word_dist.tagdict:
|
2014-08-20 11:39:39 +00:00
|
|
|
w.possible_tags |= TAGS[tag]
|
2014-08-18 17:59:59 +00:00
|
|
|
|
|
|
|
|
2014-08-18 18:23:54 +00:00
|
|
|
cdef inline bint _is_whitespace(Py_UNICODE c) nogil:
|
|
|
|
if c == ' ':
|
2014-08-18 17:59:59 +00:00
|
|
|
return True
|
2014-08-18 18:23:54 +00:00
|
|
|
elif c == '\n':
|
2014-08-18 17:59:59 +00:00
|
|
|
return True
|
2014-08-18 18:23:54 +00:00
|
|
|
elif c == '\t':
|
2014-08-18 17:59:59 +00:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2014-08-22 15:28:23 +00:00
|
|
|
#cdef inline int _extend(Tokens tokens, Lexeme** chunk) nogil:
|
|
|
|
# cdef size_t i = 0
|
|
|
|
# while chunk[i] != NULL:
|
|
|
|
# tokens.vctr[0].push_back(<Lexeme_addr>chunk[i])
|
|
|
|
# tokens.length += 1
|
|
|
|
# i += 1
|