2020-03-02 10:48:10 +00:00
|
|
|
# cython: infer_types=True, profile=True
|
🏷 Add Mypy check to CI and ignore all existing Mypy errors (#9167)
* 🚨 Ignore all existing Mypy errors
* 🏗 Add Mypy check to CI
* Add types-mock and types-requests as dev requirements
* Add additional type ignore directives
* Add types packages to dev-only list in reqs test
* Add types-dataclasses for python 3.6
* Add ignore to pretrain
* 🏷 Improve type annotation on `run_command` helper
The `run_command` helper previously declared that it returned an
`Optional[subprocess.CompletedProcess]`, but it isn't actually possible
for the function to return `None`. These changes modify the type
annotation of the `run_command` helper and remove all now-unnecessary
`# type: ignore` directives.
* 🔧 Allow variable type redefinition in limited contexts
These changes modify how Mypy is configured to allow variables to have
their type automatically redefined under certain conditions. The Mypy
documentation contains the following example:
```python
def process(items: List[str]) -> None:
# 'items' has type List[str]
items = [item.split() for item in items]
# 'items' now has type List[List[str]]
...
```
This configuration change is especially helpful in reducing the number
of `# type: ignore` directives needed to handle the common pattern of:
* Accepting a filepath as a string
* Overwriting the variable using `filepath = ensure_path(filepath)`
These changes enable redefinition and remove all `# type: ignore`
directives rendered redundant by this change.
* 🏷 Add type annotation to converters mapping
* 🚨 Fix Mypy error in convert CLI argument verification
* 🏷 Improve type annotation on `resolve_dot_names` helper
* 🏷 Add type annotations for `Vocab` attributes `strings` and `vectors`
* 🏷 Add type annotations for more `Vocab` attributes
* 🏷 Add loose type annotation for gold data compilation
* 🏷 Improve `_format_labels` type annotation
* 🏷 Fix `get_lang_class` type annotation
* 🏷 Loosen return type of `Language.evaluate`
* 🏷 Don't accept `Scorer` in `handle_scores_per_type`
* 🏷 Add `string_to_list` overloads
* 🏷 Fix non-Optional command-line options
* 🙈 Ignore redefinition of `wandb_logger` in `loggers.py`
* ➕ Install `typing_extensions` in Python 3.8+
The `typing_extensions` package states that it should be used when
"writing code that must be compatible with multiple Python versions".
Since SpaCy needs to support multiple Python versions, it should be used
when newer `typing` module members are required. One example of this is
`Literal`, which is available starting with Python 3.8.
Previously SpaCy tried to import `Literal` from `typing`, falling back
to `typing_extensions` if the import failed. However, Mypy doesn't seem
to be able to understand what `Literal` means when the initial import
means. Therefore, these changes modify how `compat` imports `Literal` by
always importing it from `typing_extensions`.
These changes also modify how `typing_extensions` is installed, so that
it is a requirement for all Python versions, including those greater
than or equal to 3.8.
* 🏷 Improve type annotation for `Language.pipe`
These changes add a missing overload variant to the type signature of
`Language.pipe`. Additionally, the type signature is enhanced to allow
type checkers to differentiate between the two overload variants based
on the `as_tuple` parameter.
Fixes #8772
* ➖ Don't install `typing-extensions` in Python 3.8+
After more detailed analysis of how to implement Python version-specific
type annotations using SpaCy, it has been determined that by branching
on a comparison against `sys.version_info` can be statically analyzed by
Mypy well enough to enable us to conditionally use
`typing_extensions.Literal`. This means that we no longer need to
install `typing_extensions` for Python versions greater than or equal to
3.8! 🎉
These changes revert previous changes installing `typing-extensions`
regardless of Python version and modify how we import the `Literal` type
to ensure that Mypy treats it properly.
* resolve mypy errors for Strict pydantic types
* refactor code to avoid missing return statement
* fix types of convert CLI command
* avoid list-set confustion in debug_data
* fix typo and formatting
* small fixes to avoid type ignores
* fix types in profile CLI command and make it more efficient
* type fixes in projects CLI
* put one ignore back
* type fixes for render
* fix render types - the sequel
* fix BaseDefault in language definitions
* fix type of noun_chunks iterator - yields tuple instead of span
* fix types in language-specific modules
* 🏷 Expand accepted inputs of `get_string_id`
`get_string_id` accepts either a string (in which case it returns its
ID) or an ID (in which case it immediately returns the ID). These
changes extend the type annotation of `get_string_id` to indicate that
it can accept either strings or IDs.
* 🏷 Handle override types in `combine_score_weights`
The `combine_score_weights` function allows users to pass an `overrides`
mapping to override data extracted from the `weights` argument. Since it
allows `Optional` dictionary values, the return value may also include
`Optional` dictionary values.
These changes update the type annotations for `combine_score_weights` to
reflect this fact.
* 🏷 Fix tokenizer serialization method signatures in `DummyTokenizer`
* 🏷 Fix redefinition of `wandb_logger`
These changes fix the redefinition of `wandb_logger` by giving a
separate name to each `WandbLogger` version. For
backwards-compatibility, `spacy.train` still exports `wandb_logger_v3`
as `wandb_logger` for now.
* more fixes for typing in language
* type fixes in model definitions
* 🏷 Annotate `_RandomWords.probs` as `NDArray`
* 🏷 Annotate `tok2vec` layers to help Mypy
* 🐛 Fix `_RandomWords.probs` type annotations for Python 3.6
Also remove an import that I forgot to move to the top of the module 😅
* more fixes for matchers and other pipeline components
* quick fix for entity linker
* fixing types for spancat, textcat, etc
* bugfix for tok2vec
* type annotations for scorer
* add runtime_checkable for Protocol
* type and import fixes in tests
* mypy fixes for training utilities
* few fixes in util
* fix import
* 🐵 Remove unused `# type: ignore` directives
* 🏷 Annotate `Language._components`
* 🏷 Annotate `spacy.pipeline.Pipe`
* add doc as property to span.pyi
* small fixes and cleanup
* explicit type annotations instead of via comment
Co-authored-by: Adriane Boyd <adrianeboyd@gmail.com>
Co-authored-by: svlandeg <sofie.vanlandeghem@gmail.com>
Co-authored-by: svlandeg <svlandeg@github.com>
2021-10-14 13:21:40 +00:00
|
|
|
from typing import Iterator, Iterable, Callable, Dict, Any
|
2020-10-08 19:33:49 +00:00
|
|
|
|
|
|
|
import srsly
|
2019-04-24 09:26:38 +00:00
|
|
|
from cymem.cymem cimport Pool
|
|
|
|
from preshed.maps cimport PreshMap
|
2019-04-23 14:33:40 +00:00
|
|
|
from cpython.exc cimport PyErr_SetFromErrno
|
2019-06-19 07:15:43 +00:00
|
|
|
from libc.stdio cimport fopen, fclose, fread, fwrite, feof, fseek
|
2019-04-23 14:33:40 +00:00
|
|
|
from libc.stdint cimport int32_t, int64_t
|
2020-05-21 18:05:03 +00:00
|
|
|
from libcpp.vector cimport vector
|
|
|
|
|
2020-02-28 11:20:23 +00:00
|
|
|
from pathlib import Path
|
2020-05-21 18:05:03 +00:00
|
|
|
import warnings
|
2020-09-22 19:53:06 +00:00
|
|
|
|
2019-04-23 14:33:40 +00:00
|
|
|
from .typedefs cimport hash_t
|
2020-05-21 18:05:03 +00:00
|
|
|
from .errors import Errors, Warnings
|
2020-10-08 19:33:49 +00:00
|
|
|
from . import util
|
|
|
|
from .util import SimpleFrozenList, ensure_path
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2019-03-20 23:04:06 +00:00
|
|
|
cdef class Candidate:
|
2019-08-13 13:38:59 +00:00
|
|
|
"""A `Candidate` object refers to a textual mention (`alias`) that may or may not be resolved
|
|
|
|
to a specific `entity` from a Knowledge Base. This will be used as input for the entity linking
|
|
|
|
algorithm which will disambiguate the various candidates to the correct one.
|
|
|
|
Each candidate (alias, entity) pair is assigned to a certain prior probability.
|
|
|
|
|
2021-01-30 09:09:38 +00:00
|
|
|
DOCS: https://spacy.io/api/kb/#candidate_init
|
2019-08-13 13:38:59 +00:00
|
|
|
"""
|
2019-03-20 23:04:06 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
def __init__(self, KnowledgeBase kb, entity_hash, entity_freq, entity_vector, alias_hash, prior_prob):
|
2019-03-21 12:26:12 +00:00
|
|
|
self.kb = kb
|
2019-03-25 17:10:41 +00:00
|
|
|
self.entity_hash = entity_hash
|
2019-04-24 21:52:34 +00:00
|
|
|
self.entity_freq = entity_freq
|
2019-06-05 16:29:18 +00:00
|
|
|
self.entity_vector = entity_vector
|
2019-03-21 11:31:02 +00:00
|
|
|
self.alias_hash = alias_hash
|
|
|
|
self.prior_prob = prior_prob
|
2019-03-20 23:04:06 +00:00
|
|
|
|
2019-03-22 15:10:49 +00:00
|
|
|
@property
|
2019-03-25 17:10:41 +00:00
|
|
|
def entity(self):
|
|
|
|
"""RETURNS (uint64): hash of the entity's KB ID/name"""
|
|
|
|
return self.entity_hash
|
2019-03-21 17:20:57 +00:00
|
|
|
|
2019-03-22 15:10:49 +00:00
|
|
|
@property
|
2019-03-25 17:10:41 +00:00
|
|
|
def entity_(self):
|
2020-05-24 15:20:58 +00:00
|
|
|
"""RETURNS (str): ID/name of this entity in the KB"""
|
2019-04-10 14:06:09 +00:00
|
|
|
return self.kb.vocab.strings[self.entity_hash]
|
2019-03-20 23:04:06 +00:00
|
|
|
|
2019-03-22 15:10:49 +00:00
|
|
|
@property
|
|
|
|
def alias(self):
|
2019-03-21 12:26:12 +00:00
|
|
|
"""RETURNS (uint64): hash of the alias"""
|
2019-03-22 15:10:49 +00:00
|
|
|
return self.alias_hash
|
2019-03-20 23:04:06 +00:00
|
|
|
|
2019-03-22 15:10:49 +00:00
|
|
|
@property
|
|
|
|
def alias_(self):
|
2020-05-24 15:20:58 +00:00
|
|
|
"""RETURNS (str): ID of the original alias"""
|
2019-04-10 14:06:09 +00:00
|
|
|
return self.kb.vocab.strings[self.alias_hash]
|
2019-03-21 17:20:57 +00:00
|
|
|
|
2019-04-24 21:52:34 +00:00
|
|
|
@property
|
|
|
|
def entity_freq(self):
|
|
|
|
return self.entity_freq
|
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
@property
|
|
|
|
def entity_vector(self):
|
|
|
|
return self.entity_vector
|
|
|
|
|
2019-03-22 15:10:49 +00:00
|
|
|
@property
|
|
|
|
def prior_prob(self):
|
|
|
|
return self.prior_prob
|
2019-03-20 23:04:06 +00:00
|
|
|
|
|
|
|
|
2020-08-18 14:10:36 +00:00
|
|
|
def get_candidates(KnowledgeBase kb, span) -> Iterator[Candidate]:
|
|
|
|
"""
|
|
|
|
Return candidate entities for a given span by using the text of the span as the alias
|
|
|
|
and fetching appropriate entries from the index.
|
|
|
|
This particular function is optimized to work with the built-in KB functionality,
|
|
|
|
but any other custom candidate generation method can be used in combination with the KB as well.
|
|
|
|
"""
|
|
|
|
return kb.get_alias_candidates(span.text)
|
|
|
|
|
|
|
|
|
2019-03-15 15:05:23 +00:00
|
|
|
cdef class KnowledgeBase:
|
2019-08-13 13:38:59 +00:00
|
|
|
"""A `KnowledgeBase` instance stores unique identifiers for entities and their textual aliases,
|
|
|
|
to support entity linking of named entities to real-world concepts.
|
|
|
|
|
2021-01-30 09:09:38 +00:00
|
|
|
DOCS: https://spacy.io/api/kb
|
2019-08-13 13:38:59 +00:00
|
|
|
"""
|
2019-06-05 16:29:18 +00:00
|
|
|
|
2020-08-18 14:10:36 +00:00
|
|
|
def __init__(self, Vocab vocab, entity_vector_length):
|
|
|
|
"""Create a KnowledgeBase."""
|
2019-03-18 16:27:51 +00:00
|
|
|
self.mem = Pool()
|
2019-06-05 16:29:18 +00:00
|
|
|
self.entity_vector_length = entity_vector_length
|
2019-04-10 15:25:10 +00:00
|
|
|
self._entry_index = PreshMap()
|
|
|
|
self._alias_index = PreshMap()
|
2020-08-04 12:34:09 +00:00
|
|
|
self.vocab = vocab
|
2019-04-10 15:25:10 +00:00
|
|
|
self._create_empty_vectors(dummy_hash=self.vocab.strings[""])
|
2019-03-18 16:27:51 +00:00
|
|
|
|
2022-06-17 13:55:34 +00:00
|
|
|
def _initialize_entities(self, int64_t nr_entities):
|
2021-05-20 08:11:30 +00:00
|
|
|
self._entry_index = PreshMap(nr_entities + 1)
|
|
|
|
self._entries = entry_vec(nr_entities + 1)
|
2021-10-19 07:39:17 +00:00
|
|
|
|
2022-06-17 13:55:34 +00:00
|
|
|
def _initialize_vectors(self, int64_t nr_entities):
|
2021-05-20 08:11:30 +00:00
|
|
|
self._vectors_table = float_matrix(nr_entities + 1)
|
|
|
|
|
2022-06-17 13:55:34 +00:00
|
|
|
def _initialize_aliases(self, int64_t nr_aliases):
|
2021-05-20 08:11:30 +00:00
|
|
|
self._alias_index = PreshMap(nr_aliases + 1)
|
|
|
|
self._aliases_table = alias_vec(nr_aliases + 1)
|
|
|
|
|
2019-06-07 10:58:42 +00:00
|
|
|
@property
|
|
|
|
def entity_vector_length(self):
|
|
|
|
"""RETURNS (uint64): length of the entity vectors"""
|
|
|
|
return self.entity_vector_length
|
|
|
|
|
2019-03-15 15:05:23 +00:00
|
|
|
def __len__(self):
|
2019-03-19 14:51:56 +00:00
|
|
|
return self.get_size_entities()
|
|
|
|
|
|
|
|
def get_size_entities(self):
|
2019-04-24 09:26:38 +00:00
|
|
|
return len(self._entry_index)
|
2019-03-15 15:05:23 +00:00
|
|
|
|
2019-04-18 12:12:17 +00:00
|
|
|
def get_entity_strings(self):
|
2019-04-24 18:24:24 +00:00
|
|
|
return [self.vocab.strings[x] for x in self._entry_index]
|
2019-04-18 12:12:17 +00:00
|
|
|
|
2019-03-19 14:51:56 +00:00
|
|
|
def get_size_aliases(self):
|
2019-04-24 09:26:38 +00:00
|
|
|
return len(self._alias_index)
|
2019-04-18 12:12:17 +00:00
|
|
|
|
|
|
|
def get_alias_strings(self):
|
2019-04-24 18:24:24 +00:00
|
|
|
return [self.vocab.strings[x] for x in self._alias_index]
|
2019-03-19 14:51:56 +00:00
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def add_entity(self, str entity, float freq, vector[float] entity_vector):
|
2019-03-21 12:26:12 +00:00
|
|
|
"""
|
2019-04-10 14:06:09 +00:00
|
|
|
Add an entity to the KB, optionally specifying its log probability based on corpus frequency
|
2019-06-05 16:29:18 +00:00
|
|
|
Return the hash of the entity ID/name at the end.
|
2019-03-21 12:26:12 +00:00
|
|
|
"""
|
2020-10-10 16:55:07 +00:00
|
|
|
cdef hash_t entity_hash = self.vocab.strings.add(entity)
|
2019-03-18 16:27:51 +00:00
|
|
|
|
2019-03-19 20:35:24 +00:00
|
|
|
# Return if this entity was added before
|
2019-03-25 17:10:41 +00:00
|
|
|
if entity_hash in self._entry_index:
|
2020-04-28 11:37:37 +00:00
|
|
|
warnings.warn(Warnings.W018.format(entity=entity))
|
2019-03-15 15:05:23 +00:00
|
|
|
return
|
|
|
|
|
2019-06-19 10:35:26 +00:00
|
|
|
# Raise an error if the provided entity vector is not of the correct length
|
2019-06-05 16:29:18 +00:00
|
|
|
if len(entity_vector) != self.entity_vector_length:
|
2019-06-19 10:35:26 +00:00
|
|
|
raise ValueError(Errors.E141.format(found=len(entity_vector), required=self.entity_vector_length))
|
2019-06-05 16:29:18 +00:00
|
|
|
|
|
|
|
vector_index = self.c_add_vector(entity_vector=entity_vector)
|
2019-04-10 15:25:10 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
new_index = self.c_add_entity(entity_hash=entity_hash,
|
2019-07-19 15:40:28 +00:00
|
|
|
freq=freq,
|
2019-06-05 16:29:18 +00:00
|
|
|
vector_index=vector_index,
|
|
|
|
feats_row=-1) # Features table currently not implemented
|
|
|
|
self._entry_index[entity_hash] = new_index
|
2019-03-15 15:05:23 +00:00
|
|
|
|
2019-03-25 17:10:41 +00:00
|
|
|
return entity_hash
|
2019-03-21 12:26:12 +00:00
|
|
|
|
2019-07-19 15:40:28 +00:00
|
|
|
cpdef set_entities(self, entity_list, freq_list, vector_list):
|
|
|
|
if len(entity_list) != len(freq_list) or len(entity_list) != len(vector_list):
|
2019-06-19 10:35:26 +00:00
|
|
|
raise ValueError(Errors.E140)
|
2019-06-06 17:51:27 +00:00
|
|
|
|
2019-12-13 09:45:29 +00:00
|
|
|
nr_entities = len(set(entity_list))
|
2022-06-17 13:55:34 +00:00
|
|
|
self._initialize_entities(nr_entities)
|
|
|
|
self._initialize_vectors(nr_entities)
|
2019-04-30 22:00:38 +00:00
|
|
|
|
|
|
|
i = 0
|
2019-06-26 13:55:26 +00:00
|
|
|
cdef KBEntryC entry
|
2019-10-14 10:28:53 +00:00
|
|
|
cdef hash_t entity_hash
|
2019-12-13 09:45:29 +00:00
|
|
|
while i < len(entity_list):
|
|
|
|
# only process this entity if its unique ID hadn't been added before
|
2020-10-10 16:55:07 +00:00
|
|
|
entity_hash = self.vocab.strings.add(entity_list[i])
|
2019-12-13 09:45:29 +00:00
|
|
|
if entity_hash in self._entry_index:
|
2020-04-28 11:37:37 +00:00
|
|
|
warnings.warn(Warnings.W018.format(entity=entity_list[i]))
|
2019-12-13 09:45:29 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
entity_vector = vector_list[i]
|
|
|
|
if len(entity_vector) != self.entity_vector_length:
|
|
|
|
raise ValueError(Errors.E141.format(found=len(entity_vector), required=self.entity_vector_length))
|
|
|
|
|
|
|
|
entry.entity_hash = entity_hash
|
|
|
|
entry.freq = freq_list[i]
|
2019-06-05 16:29:18 +00:00
|
|
|
|
2021-10-19 07:39:17 +00:00
|
|
|
self._vectors_table[i] = entity_vector
|
|
|
|
entry.vector_index = i
|
2019-06-05 16:29:18 +00:00
|
|
|
|
2019-12-13 09:45:29 +00:00
|
|
|
entry.feats_row = -1 # Features table currently not implemented
|
2019-04-30 22:00:38 +00:00
|
|
|
|
2019-12-13 09:45:29 +00:00
|
|
|
self._entries[i+1] = entry
|
|
|
|
self._entry_index[entity_hash] = i+1
|
2019-04-30 22:00:38 +00:00
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def contains_entity(self, str entity):
|
2019-10-14 10:28:53 +00:00
|
|
|
cdef hash_t entity_hash = self.vocab.strings.add(entity)
|
|
|
|
return entity_hash in self._entry_index
|
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def contains_alias(self, str alias):
|
2019-10-14 10:28:53 +00:00
|
|
|
cdef hash_t alias_hash = self.vocab.strings.add(alias)
|
|
|
|
return alias_hash in self._alias_index
|
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def add_alias(self, str alias, entities, probabilities):
|
2019-03-21 12:26:12 +00:00
|
|
|
"""
|
|
|
|
For a given alias, add its potential entities and prior probabilies to the KB.
|
|
|
|
Return the alias_hash at the end
|
|
|
|
"""
|
2021-01-29 00:51:40 +00:00
|
|
|
if alias is None or len(alias) == 0:
|
|
|
|
raise ValueError(Errors.E890.format(alias=alias))
|
|
|
|
|
|
|
|
previous_alias_nr = self.get_size_aliases()
|
2019-03-19 20:55:10 +00:00
|
|
|
# Throw an error if the length of entities and probabilities are not the same
|
|
|
|
if not len(entities) == len(probabilities):
|
2019-03-22 15:55:05 +00:00
|
|
|
raise ValueError(Errors.E132.format(alias=alias,
|
|
|
|
entities_length=len(entities),
|
|
|
|
probabilities_length=len(probabilities)))
|
2019-03-19 20:55:10 +00:00
|
|
|
|
2019-05-01 21:05:40 +00:00
|
|
|
# Throw an error if the probabilities sum up to more than 1 (allow for some rounding errors)
|
2019-03-19 20:43:48 +00:00
|
|
|
prob_sum = sum(probabilities)
|
2019-05-01 21:05:40 +00:00
|
|
|
if prob_sum > 1.00001:
|
2019-03-22 15:55:05 +00:00
|
|
|
raise ValueError(Errors.E133.format(alias=alias, sum=prob_sum))
|
2019-03-19 20:43:48 +00:00
|
|
|
|
2020-10-10 16:55:07 +00:00
|
|
|
cdef hash_t alias_hash = self.vocab.strings.add(alias)
|
2019-03-19 20:35:24 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
# Check whether this alias was added before
|
2019-03-19 20:35:24 +00:00
|
|
|
if alias_hash in self._alias_index:
|
2020-04-28 11:37:37 +00:00
|
|
|
warnings.warn(Warnings.W017.format(alias=alias))
|
2019-03-19 20:35:24 +00:00
|
|
|
return
|
|
|
|
|
2019-03-19 15:15:38 +00:00
|
|
|
cdef vector[int64_t] entry_indices
|
|
|
|
cdef vector[float] probs
|
|
|
|
|
|
|
|
for entity, prob in zip(entities, probabilities):
|
2019-03-25 17:10:41 +00:00
|
|
|
entity_hash = self.vocab.strings[entity]
|
|
|
|
if not entity_hash in self._entry_index:
|
2019-10-14 10:28:53 +00:00
|
|
|
raise ValueError(Errors.E134.format(entity=entity))
|
2019-03-19 16:39:35 +00:00
|
|
|
|
2019-03-25 17:10:41 +00:00
|
|
|
entry_index = <int64_t>self._entry_index.get(entity_hash)
|
2019-03-19 15:15:38 +00:00
|
|
|
entry_indices.push_back(int(entry_index))
|
|
|
|
probs.push_back(float(prob))
|
2019-03-18 11:38:40 +00:00
|
|
|
|
2019-04-10 15:25:10 +00:00
|
|
|
new_index = self.c_add_aliases(alias_hash=alias_hash, entry_indices=entry_indices, probs=probs)
|
|
|
|
self._alias_index[alias_hash] = new_index
|
2019-03-18 16:27:51 +00:00
|
|
|
|
2021-01-29 00:51:40 +00:00
|
|
|
if previous_alias_nr + 1 != self.get_size_aliases():
|
|
|
|
raise RuntimeError(Errors.E891.format(alias=alias))
|
2019-03-21 12:26:12 +00:00
|
|
|
return alias_hash
|
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def append_alias(self, str alias, str entity, float prior_prob, ignore_warnings=False):
|
2019-10-14 10:28:53 +00:00
|
|
|
"""
|
|
|
|
For an alias already existing in the KB, extend its potential entities with one more.
|
|
|
|
Throw a warning if either the alias or the entity is unknown,
|
|
|
|
or when the combination is already previously recorded.
|
|
|
|
Throw an error if this entity+prior prob would exceed the sum of 1.
|
|
|
|
For efficiency, it's best to use the method `add_alias` as much as possible instead of this one.
|
|
|
|
"""
|
|
|
|
# Check if the alias exists in the KB
|
|
|
|
cdef hash_t alias_hash = self.vocab.strings[alias]
|
|
|
|
if not alias_hash in self._alias_index:
|
|
|
|
raise ValueError(Errors.E176.format(alias=alias))
|
|
|
|
|
|
|
|
# Check if the entity exists in the KB
|
|
|
|
cdef hash_t entity_hash = self.vocab.strings[entity]
|
|
|
|
if not entity_hash in self._entry_index:
|
|
|
|
raise ValueError(Errors.E134.format(entity=entity))
|
|
|
|
entry_index = <int64_t>self._entry_index.get(entity_hash)
|
|
|
|
|
|
|
|
# Throw an error if the prior probabilities (including the new one) sum up to more than 1
|
|
|
|
alias_index = <int64_t>self._alias_index.get(alias_hash)
|
|
|
|
alias_entry = self._aliases_table[alias_index]
|
|
|
|
current_sum = sum([p for p in alias_entry.probs])
|
|
|
|
new_sum = current_sum + prior_prob
|
|
|
|
|
|
|
|
if new_sum > 1.00001:
|
|
|
|
raise ValueError(Errors.E133.format(alias=alias, sum=new_sum))
|
|
|
|
|
|
|
|
entry_indices = alias_entry.entry_indices
|
|
|
|
|
|
|
|
is_present = False
|
|
|
|
for i in range(entry_indices.size()):
|
|
|
|
if entry_indices[i] == int(entry_index):
|
|
|
|
is_present = True
|
|
|
|
|
|
|
|
if is_present:
|
|
|
|
if not ignore_warnings:
|
2020-04-28 11:37:37 +00:00
|
|
|
warnings.warn(Warnings.W024.format(entity=entity, alias=alias))
|
2019-10-14 10:28:53 +00:00
|
|
|
else:
|
|
|
|
entry_indices.push_back(int(entry_index))
|
|
|
|
alias_entry.entry_indices = entry_indices
|
|
|
|
|
|
|
|
probs = alias_entry.probs
|
|
|
|
probs.push_back(float(prior_prob))
|
|
|
|
alias_entry.probs = probs
|
|
|
|
self._aliases_table[alias_index] = alias_entry
|
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def get_alias_candidates(self, str alias) -> Iterator[Candidate]:
|
2019-10-14 10:28:53 +00:00
|
|
|
"""
|
|
|
|
Return candidate entities for an alias. Each candidate defines the entity, the original alias,
|
|
|
|
and the prior probability of that alias resolving to that entity.
|
|
|
|
If the alias is not known in the KB, and empty list is returned.
|
|
|
|
"""
|
2019-03-21 22:17:25 +00:00
|
|
|
cdef hash_t alias_hash = self.vocab.strings[alias]
|
2019-10-14 10:28:53 +00:00
|
|
|
if not alias_hash in self._alias_index:
|
|
|
|
return []
|
2019-07-22 11:39:32 +00:00
|
|
|
alias_index = <int64_t>self._alias_index.get(alias_hash)
|
2019-03-20 23:04:06 +00:00
|
|
|
alias_entry = self._aliases_table[alias_index]
|
|
|
|
|
2019-03-21 12:26:12 +00:00
|
|
|
return [Candidate(kb=self,
|
2019-03-25 17:10:41 +00:00
|
|
|
entity_hash=self._entries[entry_index].entity_hash,
|
2019-07-19 15:40:28 +00:00
|
|
|
entity_freq=self._entries[entry_index].freq,
|
2019-06-05 16:29:18 +00:00
|
|
|
entity_vector=self._vectors_table[self._entries[entry_index].vector_index],
|
2019-03-21 11:31:02 +00:00
|
|
|
alias_hash=alias_hash,
|
2019-07-17 15:18:26 +00:00
|
|
|
prior_prob=prior_prob)
|
|
|
|
for (entry_index, prior_prob) in zip(alias_entry.entry_indices, alias_entry.probs)
|
2019-03-21 14:24:40 +00:00
|
|
|
if entry_index != 0]
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def get_vector(self, str entity):
|
2019-07-17 15:18:26 +00:00
|
|
|
cdef hash_t entity_hash = self.vocab.strings[entity]
|
2019-07-17 10:17:02 +00:00
|
|
|
|
|
|
|
# Return an empty list if this entity is unknown in this KB
|
|
|
|
if entity_hash not in self._entry_index:
|
2019-07-18 08:22:24 +00:00
|
|
|
return [0] * self.entity_vector_length
|
2019-07-17 10:17:02 +00:00
|
|
|
entry_index = self._entry_index[entity_hash]
|
|
|
|
|
|
|
|
return self._vectors_table[self._entries[entry_index].vector_index]
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2021-09-13 15:02:17 +00:00
|
|
|
def get_prior_prob(self, str entity, str alias):
|
2019-07-17 15:18:26 +00:00
|
|
|
""" Return the prior probability of a given alias being linked to a given entity,
|
|
|
|
or return 0.0 when this combination is not known in the knowledge base"""
|
|
|
|
cdef hash_t alias_hash = self.vocab.strings[alias]
|
|
|
|
cdef hash_t entity_hash = self.vocab.strings[entity]
|
|
|
|
|
|
|
|
if entity_hash not in self._entry_index or alias_hash not in self._alias_index:
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
alias_index = <int64_t>self._alias_index.get(alias_hash)
|
|
|
|
entry_index = self._entry_index[entity_hash]
|
|
|
|
|
|
|
|
alias_entry = self._aliases_table[alias_index]
|
|
|
|
for (entry_index, prior_prob) in zip(alias_entry.entry_indices, alias_entry.probs):
|
|
|
|
if self._entries[entry_index].entity_hash == entity_hash:
|
|
|
|
return prior_prob
|
|
|
|
|
|
|
|
return 0.0
|
|
|
|
|
2021-05-20 08:11:30 +00:00
|
|
|
def to_bytes(self, **kwargs):
|
|
|
|
"""Serialize the current state to a binary string.
|
|
|
|
"""
|
|
|
|
def serialize_header():
|
|
|
|
header = (self.get_size_entities(), self.get_size_aliases(), self.entity_vector_length)
|
|
|
|
return srsly.json_dumps(header)
|
|
|
|
|
|
|
|
def serialize_entries():
|
|
|
|
i = 1
|
|
|
|
tuples = []
|
|
|
|
for entry_hash, entry_index in sorted(self._entry_index.items(), key=lambda x: x[1]):
|
|
|
|
entry = self._entries[entry_index]
|
|
|
|
assert entry.entity_hash == entry_hash
|
|
|
|
assert entry_index == i
|
|
|
|
tuples.append((entry.entity_hash, entry.freq, entry.vector_index))
|
|
|
|
i = i + 1
|
|
|
|
return srsly.json_dumps(tuples)
|
|
|
|
|
|
|
|
def serialize_aliases():
|
|
|
|
i = 1
|
|
|
|
headers = []
|
|
|
|
indices_lists = []
|
|
|
|
probs_lists = []
|
|
|
|
for alias_hash, alias_index in sorted(self._alias_index.items(), key=lambda x: x[1]):
|
|
|
|
alias = self._aliases_table[alias_index]
|
|
|
|
assert alias_index == i
|
|
|
|
candidate_length = len(alias.entry_indices)
|
|
|
|
headers.append((alias_hash, candidate_length))
|
|
|
|
indices_lists.append(alias.entry_indices)
|
|
|
|
probs_lists.append(alias.probs)
|
|
|
|
i = i + 1
|
|
|
|
headers_dump = srsly.json_dumps(headers)
|
|
|
|
indices_dump = srsly.json_dumps(indices_lists)
|
|
|
|
probs_dump = srsly.json_dumps(probs_lists)
|
|
|
|
return srsly.json_dumps((headers_dump, indices_dump, probs_dump))
|
|
|
|
|
|
|
|
serializers = {
|
|
|
|
"header": serialize_header,
|
|
|
|
"entity_vectors": lambda: srsly.json_dumps(self._vectors_table),
|
|
|
|
"entries": serialize_entries,
|
|
|
|
"aliases": serialize_aliases,
|
|
|
|
}
|
|
|
|
return util.to_bytes(serializers, [])
|
|
|
|
|
|
|
|
def from_bytes(self, bytes_data, *, exclude=tuple()):
|
|
|
|
"""Load state from a binary string.
|
|
|
|
"""
|
|
|
|
def deserialize_header(b):
|
|
|
|
header = srsly.json_loads(b)
|
|
|
|
nr_entities = header[0]
|
|
|
|
nr_aliases = header[1]
|
|
|
|
entity_vector_length = header[2]
|
2022-06-17 13:55:34 +00:00
|
|
|
self._initialize_entities(nr_entities)
|
|
|
|
self._initialize_vectors(nr_entities)
|
|
|
|
self._initialize_aliases(nr_aliases)
|
2021-05-20 08:11:30 +00:00
|
|
|
self.entity_vector_length = entity_vector_length
|
|
|
|
|
|
|
|
def deserialize_vectors(b):
|
|
|
|
self._vectors_table = srsly.json_loads(b)
|
|
|
|
|
|
|
|
def deserialize_entries(b):
|
|
|
|
cdef KBEntryC entry
|
|
|
|
tuples = srsly.json_loads(b)
|
|
|
|
i = 1
|
|
|
|
for (entity_hash, freq, vector_index) in tuples:
|
|
|
|
entry.entity_hash = entity_hash
|
|
|
|
entry.freq = freq
|
|
|
|
entry.vector_index = vector_index
|
|
|
|
entry.feats_row = -1 # Features table currently not implemented
|
|
|
|
self._entries[i] = entry
|
|
|
|
self._entry_index[entity_hash] = i
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
def deserialize_aliases(b):
|
|
|
|
cdef AliasC alias
|
|
|
|
i = 1
|
|
|
|
all_data = srsly.json_loads(b)
|
|
|
|
headers = srsly.json_loads(all_data[0])
|
|
|
|
indices = srsly.json_loads(all_data[1])
|
|
|
|
probs = srsly.json_loads(all_data[2])
|
|
|
|
for header, indices, probs in zip(headers, indices, probs):
|
|
|
|
alias_hash, candidate_length = header
|
|
|
|
alias.entry_indices = indices
|
|
|
|
alias.probs = probs
|
|
|
|
self._aliases_table[i] = alias
|
|
|
|
self._alias_index[alias_hash] = i
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
setters = {
|
|
|
|
"header": deserialize_header,
|
|
|
|
"entity_vectors": deserialize_vectors,
|
|
|
|
"entries": deserialize_entries,
|
|
|
|
"aliases": deserialize_aliases,
|
|
|
|
}
|
|
|
|
util.from_bytes(bytes_data, setters, exclude)
|
|
|
|
return self
|
|
|
|
|
2020-10-08 19:33:49 +00:00
|
|
|
def to_disk(self, path, exclude: Iterable[str] = SimpleFrozenList()):
|
|
|
|
path = ensure_path(path)
|
2020-09-24 14:53:59 +00:00
|
|
|
if not path.exists():
|
|
|
|
path.mkdir(parents=True)
|
|
|
|
if not path.is_dir():
|
2020-09-22 19:53:06 +00:00
|
|
|
raise ValueError(Errors.E928.format(loc=path))
|
2020-10-08 19:33:49 +00:00
|
|
|
serialize = {}
|
|
|
|
serialize["contents"] = lambda p: self.write_contents(p)
|
2020-10-10 16:55:07 +00:00
|
|
|
serialize["strings.json"] = lambda p: self.vocab.strings.to_disk(p)
|
2020-10-08 19:33:49 +00:00
|
|
|
util.to_disk(path, serialize, exclude)
|
2020-09-22 19:53:06 +00:00
|
|
|
|
2020-10-08 19:33:49 +00:00
|
|
|
def from_disk(self, path, exclude: Iterable[str] = SimpleFrozenList()):
|
|
|
|
path = ensure_path(path)
|
2020-09-24 14:53:59 +00:00
|
|
|
if not path.exists():
|
|
|
|
raise ValueError(Errors.E929.format(loc=path))
|
|
|
|
if not path.is_dir():
|
|
|
|
raise ValueError(Errors.E928.format(loc=path))
|
🏷 Add Mypy check to CI and ignore all existing Mypy errors (#9167)
* 🚨 Ignore all existing Mypy errors
* 🏗 Add Mypy check to CI
* Add types-mock and types-requests as dev requirements
* Add additional type ignore directives
* Add types packages to dev-only list in reqs test
* Add types-dataclasses for python 3.6
* Add ignore to pretrain
* 🏷 Improve type annotation on `run_command` helper
The `run_command` helper previously declared that it returned an
`Optional[subprocess.CompletedProcess]`, but it isn't actually possible
for the function to return `None`. These changes modify the type
annotation of the `run_command` helper and remove all now-unnecessary
`# type: ignore` directives.
* 🔧 Allow variable type redefinition in limited contexts
These changes modify how Mypy is configured to allow variables to have
their type automatically redefined under certain conditions. The Mypy
documentation contains the following example:
```python
def process(items: List[str]) -> None:
# 'items' has type List[str]
items = [item.split() for item in items]
# 'items' now has type List[List[str]]
...
```
This configuration change is especially helpful in reducing the number
of `# type: ignore` directives needed to handle the common pattern of:
* Accepting a filepath as a string
* Overwriting the variable using `filepath = ensure_path(filepath)`
These changes enable redefinition and remove all `# type: ignore`
directives rendered redundant by this change.
* 🏷 Add type annotation to converters mapping
* 🚨 Fix Mypy error in convert CLI argument verification
* 🏷 Improve type annotation on `resolve_dot_names` helper
* 🏷 Add type annotations for `Vocab` attributes `strings` and `vectors`
* 🏷 Add type annotations for more `Vocab` attributes
* 🏷 Add loose type annotation for gold data compilation
* 🏷 Improve `_format_labels` type annotation
* 🏷 Fix `get_lang_class` type annotation
* 🏷 Loosen return type of `Language.evaluate`
* 🏷 Don't accept `Scorer` in `handle_scores_per_type`
* 🏷 Add `string_to_list` overloads
* 🏷 Fix non-Optional command-line options
* 🙈 Ignore redefinition of `wandb_logger` in `loggers.py`
* ➕ Install `typing_extensions` in Python 3.8+
The `typing_extensions` package states that it should be used when
"writing code that must be compatible with multiple Python versions".
Since SpaCy needs to support multiple Python versions, it should be used
when newer `typing` module members are required. One example of this is
`Literal`, which is available starting with Python 3.8.
Previously SpaCy tried to import `Literal` from `typing`, falling back
to `typing_extensions` if the import failed. However, Mypy doesn't seem
to be able to understand what `Literal` means when the initial import
means. Therefore, these changes modify how `compat` imports `Literal` by
always importing it from `typing_extensions`.
These changes also modify how `typing_extensions` is installed, so that
it is a requirement for all Python versions, including those greater
than or equal to 3.8.
* 🏷 Improve type annotation for `Language.pipe`
These changes add a missing overload variant to the type signature of
`Language.pipe`. Additionally, the type signature is enhanced to allow
type checkers to differentiate between the two overload variants based
on the `as_tuple` parameter.
Fixes #8772
* ➖ Don't install `typing-extensions` in Python 3.8+
After more detailed analysis of how to implement Python version-specific
type annotations using SpaCy, it has been determined that by branching
on a comparison against `sys.version_info` can be statically analyzed by
Mypy well enough to enable us to conditionally use
`typing_extensions.Literal`. This means that we no longer need to
install `typing_extensions` for Python versions greater than or equal to
3.8! 🎉
These changes revert previous changes installing `typing-extensions`
regardless of Python version and modify how we import the `Literal` type
to ensure that Mypy treats it properly.
* resolve mypy errors for Strict pydantic types
* refactor code to avoid missing return statement
* fix types of convert CLI command
* avoid list-set confustion in debug_data
* fix typo and formatting
* small fixes to avoid type ignores
* fix types in profile CLI command and make it more efficient
* type fixes in projects CLI
* put one ignore back
* type fixes for render
* fix render types - the sequel
* fix BaseDefault in language definitions
* fix type of noun_chunks iterator - yields tuple instead of span
* fix types in language-specific modules
* 🏷 Expand accepted inputs of `get_string_id`
`get_string_id` accepts either a string (in which case it returns its
ID) or an ID (in which case it immediately returns the ID). These
changes extend the type annotation of `get_string_id` to indicate that
it can accept either strings or IDs.
* 🏷 Handle override types in `combine_score_weights`
The `combine_score_weights` function allows users to pass an `overrides`
mapping to override data extracted from the `weights` argument. Since it
allows `Optional` dictionary values, the return value may also include
`Optional` dictionary values.
These changes update the type annotations for `combine_score_weights` to
reflect this fact.
* 🏷 Fix tokenizer serialization method signatures in `DummyTokenizer`
* 🏷 Fix redefinition of `wandb_logger`
These changes fix the redefinition of `wandb_logger` by giving a
separate name to each `WandbLogger` version. For
backwards-compatibility, `spacy.train` still exports `wandb_logger_v3`
as `wandb_logger` for now.
* more fixes for typing in language
* type fixes in model definitions
* 🏷 Annotate `_RandomWords.probs` as `NDArray`
* 🏷 Annotate `tok2vec` layers to help Mypy
* 🐛 Fix `_RandomWords.probs` type annotations for Python 3.6
Also remove an import that I forgot to move to the top of the module 😅
* more fixes for matchers and other pipeline components
* quick fix for entity linker
* fixing types for spancat, textcat, etc
* bugfix for tok2vec
* type annotations for scorer
* add runtime_checkable for Protocol
* type and import fixes in tests
* mypy fixes for training utilities
* few fixes in util
* fix import
* 🐵 Remove unused `# type: ignore` directives
* 🏷 Annotate `Language._components`
* 🏷 Annotate `spacy.pipeline.Pipe`
* add doc as property to span.pyi
* small fixes and cleanup
* explicit type annotations instead of via comment
Co-authored-by: Adriane Boyd <adrianeboyd@gmail.com>
Co-authored-by: svlandeg <sofie.vanlandeghem@gmail.com>
Co-authored-by: svlandeg <svlandeg@github.com>
2021-10-14 13:21:40 +00:00
|
|
|
deserialize: Dict[str, Callable[[Any], Any]] = {}
|
2020-10-08 19:33:49 +00:00
|
|
|
deserialize["contents"] = lambda p: self.read_contents(p)
|
2020-10-10 16:55:07 +00:00
|
|
|
deserialize["strings.json"] = lambda p: self.vocab.strings.from_disk(p)
|
2020-10-08 19:33:49 +00:00
|
|
|
util.from_disk(path, deserialize, exclude)
|
2020-09-24 14:53:59 +00:00
|
|
|
|
|
|
|
def write_contents(self, file_path):
|
|
|
|
cdef Writer writer = Writer(file_path)
|
2019-06-05 16:29:18 +00:00
|
|
|
writer.write_header(self.get_size_entities(), self.entity_vector_length)
|
|
|
|
|
|
|
|
# dumping the entity vectors in their original order
|
|
|
|
i = 0
|
|
|
|
for entity_vector in self._vectors_table:
|
|
|
|
for element in entity_vector:
|
|
|
|
writer.write_vector_element(element)
|
|
|
|
i = i+1
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2019-04-24 09:26:38 +00:00
|
|
|
# dumping the entry records in the order in which they are in the _entries vector.
|
|
|
|
# index 0 is a dummy object not stored in the _entry_index and can be ignored.
|
|
|
|
i = 1
|
|
|
|
for entry_hash, entry_index in sorted(self._entry_index.items(), key=lambda x: x[1]):
|
2019-04-23 16:36:50 +00:00
|
|
|
entry = self._entries[entry_index]
|
2019-06-05 16:29:18 +00:00
|
|
|
assert entry.entity_hash == entry_hash
|
2019-04-24 09:26:38 +00:00
|
|
|
assert entry_index == i
|
2019-07-19 15:40:28 +00:00
|
|
|
writer.write_entry(entry.entity_hash, entry.freq, entry.vector_index)
|
2019-04-24 18:24:24 +00:00
|
|
|
i = i+1
|
|
|
|
|
|
|
|
writer.write_alias_length(self.get_size_aliases())
|
|
|
|
|
|
|
|
# dumping the aliases in the order in which they are in the _alias_index vector.
|
|
|
|
# index 0 is a dummy object not stored in the _aliases_table and can be ignored.
|
|
|
|
i = 1
|
|
|
|
for alias_hash, alias_index in sorted(self._alias_index.items(), key=lambda x: x[1]):
|
|
|
|
alias = self._aliases_table[alias_index]
|
|
|
|
assert alias_index == i
|
|
|
|
|
|
|
|
candidate_length = len(alias.entry_indices)
|
|
|
|
writer.write_alias_header(alias_hash, candidate_length)
|
|
|
|
|
|
|
|
for j in range(0, candidate_length):
|
|
|
|
writer.write_alias(alias.entry_indices[j], alias.probs[j])
|
|
|
|
|
2019-04-24 09:26:38 +00:00
|
|
|
i = i+1
|
2019-04-23 14:33:40 +00:00
|
|
|
|
|
|
|
writer.close()
|
|
|
|
|
2020-09-24 14:53:59 +00:00
|
|
|
def read_contents(self, file_path):
|
2019-04-23 14:33:40 +00:00
|
|
|
cdef hash_t entity_hash
|
2019-04-24 18:24:24 +00:00
|
|
|
cdef hash_t alias_hash
|
|
|
|
cdef int64_t entry_index
|
2019-07-22 11:34:12 +00:00
|
|
|
cdef float freq, prob
|
2019-06-05 16:29:18 +00:00
|
|
|
cdef int32_t vector_index
|
2019-06-26 13:55:26 +00:00
|
|
|
cdef KBEntryC entry
|
2019-04-24 18:24:24 +00:00
|
|
|
cdef AliasC alias
|
2019-06-05 16:29:18 +00:00
|
|
|
cdef float vector_element
|
2019-04-23 16:36:50 +00:00
|
|
|
|
2020-09-24 14:53:59 +00:00
|
|
|
cdef Reader reader = Reader(file_path)
|
2019-04-24 18:24:24 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
# STEP 0: load header and initialize KB
|
2019-04-24 13:31:44 +00:00
|
|
|
cdef int64_t nr_entities
|
2019-06-05 16:29:18 +00:00
|
|
|
cdef int64_t entity_vector_length
|
|
|
|
reader.read_header(&nr_entities, &entity_vector_length)
|
|
|
|
|
2022-06-17 13:55:34 +00:00
|
|
|
self._initialize_entities(nr_entities)
|
|
|
|
self._initialize_vectors(nr_entities)
|
2019-06-05 16:29:18 +00:00
|
|
|
self.entity_vector_length = entity_vector_length
|
2019-04-24 09:26:38 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
# STEP 1: load entity vectors
|
|
|
|
cdef int i = 0
|
|
|
|
cdef int j = 0
|
|
|
|
while i < nr_entities:
|
|
|
|
entity_vector = float_vec(entity_vector_length)
|
|
|
|
j = 0
|
|
|
|
while j < entity_vector_length:
|
|
|
|
reader.read_vector_element(&vector_element)
|
|
|
|
entity_vector[j] = vector_element
|
|
|
|
j = j+1
|
|
|
|
self._vectors_table[i] = entity_vector
|
|
|
|
i = i+1
|
|
|
|
|
|
|
|
# STEP 2: load entities
|
2019-04-24 18:24:24 +00:00
|
|
|
# we assume that the entity data was written in sequence
|
2019-04-24 09:26:38 +00:00
|
|
|
# index 0 is a dummy object not stored in the _entry_index and can be ignored.
|
2019-06-05 16:29:18 +00:00
|
|
|
i = 1
|
2019-04-24 18:24:24 +00:00
|
|
|
while i <= nr_entities:
|
2019-07-19 15:40:28 +00:00
|
|
|
reader.read_entry(&entity_hash, &freq, &vector_index)
|
2019-04-24 09:26:38 +00:00
|
|
|
|
2019-04-23 16:36:50 +00:00
|
|
|
entry.entity_hash = entity_hash
|
2019-07-19 15:40:28 +00:00
|
|
|
entry.freq = freq
|
2019-06-05 16:29:18 +00:00
|
|
|
entry.vector_index = vector_index
|
|
|
|
entry.feats_row = -1 # Features table currently not implemented
|
2019-04-23 16:36:50 +00:00
|
|
|
|
2019-04-24 09:26:38 +00:00
|
|
|
self._entries[i] = entry
|
|
|
|
self._entry_index[entity_hash] = i
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
2019-04-24 18:24:24 +00:00
|
|
|
# check that all entities were read in properly
|
|
|
|
assert nr_entities == self.get_size_entities()
|
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
# STEP 3: load aliases
|
2019-04-24 18:24:24 +00:00
|
|
|
cdef int64_t nr_aliases
|
|
|
|
reader.read_alias_length(&nr_aliases)
|
2022-06-17 13:55:34 +00:00
|
|
|
self._initialize_aliases(nr_aliases)
|
2019-04-24 18:24:24 +00:00
|
|
|
|
|
|
|
cdef int64_t nr_candidates
|
|
|
|
cdef vector[int64_t] entry_indices
|
|
|
|
cdef vector[float] probs
|
|
|
|
|
|
|
|
i = 1
|
|
|
|
# we assume the alias data was written in sequence
|
|
|
|
# index 0 is a dummy object not stored in the _entry_index and can be ignored.
|
|
|
|
while i <= nr_aliases:
|
|
|
|
reader.read_alias_header(&alias_hash, &nr_candidates)
|
|
|
|
entry_indices = vector[int64_t](nr_candidates)
|
|
|
|
probs = vector[float](nr_candidates)
|
|
|
|
|
|
|
|
for j in range(0, nr_candidates):
|
|
|
|
reader.read_alias(&entry_index, &prob)
|
|
|
|
entry_indices[j] = entry_index
|
|
|
|
probs[j] = prob
|
|
|
|
|
|
|
|
alias.entry_indices = entry_indices
|
|
|
|
alias.probs = probs
|
|
|
|
|
|
|
|
self._aliases_table[i] = alias
|
|
|
|
self._alias_index[alias_hash] = i
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
# check that all aliases were read in properly
|
|
|
|
assert nr_aliases == self.get_size_aliases()
|
|
|
|
|
2019-04-23 14:33:40 +00:00
|
|
|
|
|
|
|
cdef class Writer:
|
2020-09-22 19:53:06 +00:00
|
|
|
def __init__(self, path):
|
|
|
|
assert isinstance(path, Path)
|
|
|
|
content = bytes(path)
|
2021-09-13 15:02:17 +00:00
|
|
|
cdef bytes bytes_loc = content.encode('utf8') if type(content) == str else content
|
2019-04-23 14:33:40 +00:00
|
|
|
self._fp = fopen(<char*>bytes_loc, 'wb')
|
2019-07-22 12:56:13 +00:00
|
|
|
if not self._fp:
|
2020-09-22 19:53:06 +00:00
|
|
|
raise IOError(Errors.E146.format(path=path))
|
2019-04-23 14:33:40 +00:00
|
|
|
fseek(self._fp, 0, 0)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
cdef size_t status = fclose(self._fp)
|
|
|
|
assert status == 0
|
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
cdef int write_header(self, int64_t nr_entries, int64_t entity_vector_length) except -1:
|
2019-04-24 13:31:44 +00:00
|
|
|
self._write(&nr_entries, sizeof(nr_entries))
|
2019-06-05 16:29:18 +00:00
|
|
|
self._write(&entity_vector_length, sizeof(entity_vector_length))
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
cdef int write_vector_element(self, float element) except -1:
|
|
|
|
self._write(&element, sizeof(element))
|
|
|
|
|
2019-07-19 15:40:28 +00:00
|
|
|
cdef int write_entry(self, hash_t entry_hash, float entry_freq, int32_t vector_index) except -1:
|
2019-04-24 13:31:44 +00:00
|
|
|
self._write(&entry_hash, sizeof(entry_hash))
|
2019-07-19 15:40:28 +00:00
|
|
|
self._write(&entry_freq, sizeof(entry_freq))
|
2019-06-05 16:29:18 +00:00
|
|
|
self._write(&vector_index, sizeof(vector_index))
|
|
|
|
# Features table currently not implemented and not written to file
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2019-04-24 18:24:24 +00:00
|
|
|
cdef int write_alias_length(self, int64_t alias_length) except -1:
|
|
|
|
self._write(&alias_length, sizeof(alias_length))
|
|
|
|
|
|
|
|
cdef int write_alias_header(self, hash_t alias_hash, int64_t candidate_length) except -1:
|
|
|
|
self._write(&alias_hash, sizeof(alias_hash))
|
|
|
|
self._write(&candidate_length, sizeof(candidate_length))
|
|
|
|
|
|
|
|
cdef int write_alias(self, int64_t entry_index, float prob) except -1:
|
|
|
|
self._write(&entry_index, sizeof(entry_index))
|
|
|
|
self._write(&prob, sizeof(prob))
|
|
|
|
|
2019-04-24 13:31:44 +00:00
|
|
|
cdef int _write(self, void* value, size_t size) except -1:
|
|
|
|
status = fwrite(value, size, 1, self._fp)
|
|
|
|
assert status == 1, status
|
2019-04-23 14:33:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
cdef class Reader:
|
2020-09-22 19:53:06 +00:00
|
|
|
def __init__(self, path):
|
|
|
|
content = bytes(path)
|
2021-09-13 15:02:17 +00:00
|
|
|
cdef bytes bytes_loc = content.encode('utf8') if type(content) == str else content
|
2019-04-23 14:33:40 +00:00
|
|
|
self._fp = fopen(<char*>bytes_loc, 'rb')
|
|
|
|
if not self._fp:
|
|
|
|
PyErr_SetFromErrno(IOError)
|
|
|
|
status = fseek(self._fp, 0, 0) # this can be 0 if there is no header
|
|
|
|
|
|
|
|
def __dealloc__(self):
|
|
|
|
fclose(self._fp)
|
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
cdef int read_header(self, int64_t* nr_entries, int64_t* entity_vector_length) except -1:
|
2019-04-24 13:31:44 +00:00
|
|
|
status = self._read(nr_entries, sizeof(int64_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="header"))
|
2019-04-24 13:31:44 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
status = self._read(entity_vector_length, sizeof(int64_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="vector length"))
|
2019-06-05 16:29:18 +00:00
|
|
|
|
|
|
|
cdef int read_vector_element(self, float* element) except -1:
|
|
|
|
status = self._read(element, sizeof(float))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="vector element"))
|
2019-06-05 16:29:18 +00:00
|
|
|
|
2019-07-19 15:40:28 +00:00
|
|
|
cdef int read_entry(self, hash_t* entity_hash, float* freq, int32_t* vector_index) except -1:
|
2019-04-24 13:31:44 +00:00
|
|
|
status = self._read(entity_hash, sizeof(hash_t))
|
2019-04-23 14:33:40 +00:00
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="entity hash"))
|
2019-04-23 14:33:40 +00:00
|
|
|
|
2019-07-19 15:40:28 +00:00
|
|
|
status = self._read(freq, sizeof(float))
|
2019-04-23 14:33:40 +00:00
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="entity freq"))
|
2019-04-23 16:36:50 +00:00
|
|
|
|
2019-06-05 16:29:18 +00:00
|
|
|
status = self._read(vector_index, sizeof(int32_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="vector index"))
|
2019-06-05 16:29:18 +00:00
|
|
|
|
2019-04-23 16:36:50 +00:00
|
|
|
if feof(self._fp):
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return 1
|
2019-04-24 09:26:38 +00:00
|
|
|
|
2019-04-24 18:24:24 +00:00
|
|
|
cdef int read_alias_length(self, int64_t* alias_length) except -1:
|
|
|
|
status = self._read(alias_length, sizeof(int64_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="alias length"))
|
2019-04-24 18:24:24 +00:00
|
|
|
|
|
|
|
cdef int read_alias_header(self, hash_t* alias_hash, int64_t* candidate_length) except -1:
|
|
|
|
status = self._read(alias_hash, sizeof(hash_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="alias hash"))
|
2019-04-24 18:24:24 +00:00
|
|
|
|
|
|
|
status = self._read(candidate_length, sizeof(int64_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="candidate length"))
|
2019-04-24 18:24:24 +00:00
|
|
|
|
|
|
|
cdef int read_alias(self, int64_t* entry_index, float* prob) except -1:
|
|
|
|
status = self._read(entry_index, sizeof(int64_t))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="entry index"))
|
2019-04-24 18:24:24 +00:00
|
|
|
|
|
|
|
status = self._read(prob, sizeof(float))
|
|
|
|
if status < 1:
|
|
|
|
if feof(self._fp):
|
|
|
|
return 0 # end of file
|
2019-07-22 12:36:07 +00:00
|
|
|
raise IOError(Errors.E145.format(param="prior probability"))
|
2019-04-24 18:24:24 +00:00
|
|
|
|
2019-04-24 13:31:44 +00:00
|
|
|
cdef int _read(self, void* value, size_t size) except -1:
|
|
|
|
status = fread(value, size, 1, self._fp)
|
|
|
|
return status
|