mirror of
				https://github.com/explosion/spaCy.git
				synced 2025-11-01 00:17:44 +03:00 
			
		
		
		
	Move Entity Linker v1 to spacy-legacy (#12006)
* Move Entity Linker v1 component to spacy-legacy This is a follow up to #11889 that moves the component instead of removing it. In general, we never import from spacy-legacy in spaCy proper. However, to use this component, that kind of import will be necessary. I was able to test this without issues, but is this current import strategy acceptable? Or should we put the component in a registry? * Use spacy-legacy pr for CI This will need to be reverted before merging. * Add temporary step to log installed spacy-legacy version * Modify requirements.txt to trigger tests * Add comment to Python to trigger tests * TODO REVERT This is a commit with logic changes to trigger tests * Remove pipe from YAML Works locally, but possibly this is causing a quoting error or something. * Revert "TODO REVERT This is a commit with logic changes to trigger tests" This reverts commit689fae71f3. * Revert "Add comment to Python to trigger tests" This reverts commit11840fc598. * Add more logging * Try installing directly in workflow * Try explicitly uninstalling spacy-legacy first * Cat requirements.txt to confirm contents In the branch, the thinc version spec is `thinc>=8.1.0,<8.2.0`. But in the logs, it's clear that a development release of 9.0 is being installed. It's not clear why that would happen. * Log requirements at start of build * TODO REVERT Change thinc spec Want to see what happens to the installed thinc spec with this change. * Update thinc requirements This makes it the same as it was before the merge, >=8.1.0,<8.2.0. * Use same thinc version as v4 branch * TODO REVERT Mark dependency check as xfail spacy-legacy is specified as a git checkout in requirements.txt while this PR is in progress, which makes the consistency check here fail. * Remove debugging output / install step * Revert "Remove debugging output / install step" This reverts commit923ea7448b. * Clean up debugging output The manual install step with the URL fragment seems to have caused issues on Windows due to the = in the URL being misinterpreted. On the other hand, removing it seems to mean the git version of spacy-legacy isn't actually installed. This PR removes the URL fragment but keeps the direct command-line install. Additionally, since it looks like this job is configured to use the default shell (and not bash), it removes a comment that upsets the Windows cmd shell. * Revert "TODO REVERT Mark dependency check as xfail" This reverts commitd4863ec156. * Fix requirements.txt, increasing spacy-legacy version * Raise spacy legacy version in setup.cfg * Remove azure build workarounds * make spacy-legacy version explicit in error message * Remove debugging line * Suggestions from code review
This commit is contained in:
		
							parent
							
								
									360ccf628a
								
							
						
					
					
						commit
						6920fb7baf
					
				|  | @ -13,7 +13,6 @@ from ..kb import KnowledgeBase, Candidate | ||||||
| from ..ml import empty_kb | from ..ml import empty_kb | ||||||
| from ..tokens import Doc, Span | from ..tokens import Doc, Span | ||||||
| from .pipe import deserialize_config | from .pipe import deserialize_config | ||||||
| from .legacy.entity_linker import EntityLinker_v1 |  | ||||||
| from .trainable_pipe import TrainablePipe | from .trainable_pipe import TrainablePipe | ||||||
| from ..language import Language | from ..language import Language | ||||||
| from ..vocab import Vocab | from ..vocab import Vocab | ||||||
|  | @ -120,6 +119,12 @@ def make_entity_linker( | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     if not model.attrs.get("include_span_maker", False): |     if not model.attrs.get("include_span_maker", False): | ||||||
|  |         try: | ||||||
|  |             from spacy_legacy.components.entity_linker import EntityLinker_v1 | ||||||
|  |         except: | ||||||
|  |             raise ImportError( | ||||||
|  |                 "In order to use v1 of the EntityLinker, you must use spacy-legacy>=3.0.12." | ||||||
|  |             ) | ||||||
|         # The only difference in arguments here is that use_gold_ents and threshold aren't available. |         # The only difference in arguments here is that use_gold_ents and threshold aren't available. | ||||||
|         return EntityLinker_v1( |         return EntityLinker_v1( | ||||||
|             nlp.vocab, |             nlp.vocab, | ||||||
|  |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| from .entity_linker import EntityLinker_v1 |  | ||||||
| 
 |  | ||||||
| __all__ = ["EntityLinker_v1"] |  | ||||||
|  | @ -1,422 +0,0 @@ | ||||||
| # This file is present to provide a prior version of the EntityLinker component |  | ||||||
| # for backwards compatability. For details see #9669. |  | ||||||
| 
 |  | ||||||
| from typing import Optional, Iterable, Callable, Dict, Union, List, Any |  | ||||||
| from thinc.types import Floats2d |  | ||||||
| from pathlib import Path |  | ||||||
| from itertools import islice |  | ||||||
| import srsly |  | ||||||
| import random |  | ||||||
| from thinc.api import CosineDistance, Model, Optimizer |  | ||||||
| from thinc.api import set_dropout_rate |  | ||||||
| import warnings |  | ||||||
| 
 |  | ||||||
| from ...kb import KnowledgeBase, Candidate |  | ||||||
| from ...ml import empty_kb |  | ||||||
| from ...tokens import Doc, Span |  | ||||||
| from ..pipe import deserialize_config |  | ||||||
| from ..trainable_pipe import TrainablePipe |  | ||||||
| from ...language import Language |  | ||||||
| from ...vocab import Vocab |  | ||||||
| from ...training import Example, validate_examples, validate_get_examples |  | ||||||
| from ...errors import Errors, Warnings |  | ||||||
| from ...util import SimpleFrozenList |  | ||||||
| from ... import util |  | ||||||
| from ...scorer import Scorer |  | ||||||
| 
 |  | ||||||
| # See #9050 |  | ||||||
| BACKWARD_OVERWRITE = True |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def entity_linker_score(examples, **kwargs): |  | ||||||
|     return Scorer.score_links(examples, negative_labels=[EntityLinker_v1.NIL], **kwargs) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class EntityLinker_v1(TrainablePipe): |  | ||||||
|     """Pipeline component for named entity linking. |  | ||||||
| 
 |  | ||||||
|     DOCS: https://spacy.io/api/entitylinker |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     NIL = "NIL"  # string used to refer to a non-existing link |  | ||||||
| 
 |  | ||||||
|     def __init__( |  | ||||||
|         self, |  | ||||||
|         vocab: Vocab, |  | ||||||
|         model: Model, |  | ||||||
|         name: str = "entity_linker", |  | ||||||
|         *, |  | ||||||
|         labels_discard: Iterable[str], |  | ||||||
|         n_sents: int, |  | ||||||
|         incl_prior: bool, |  | ||||||
|         incl_context: bool, |  | ||||||
|         entity_vector_length: int, |  | ||||||
|         get_candidates: Callable[[KnowledgeBase, Span], Iterable[Candidate]], |  | ||||||
|         overwrite: bool = BACKWARD_OVERWRITE, |  | ||||||
|         scorer: Optional[Callable] = entity_linker_score, |  | ||||||
|     ) -> None: |  | ||||||
|         """Initialize an entity linker. |  | ||||||
| 
 |  | ||||||
|         vocab (Vocab): The shared vocabulary. |  | ||||||
|         model (thinc.api.Model): The Thinc Model powering the pipeline component. |  | ||||||
|         name (str): The component instance name, used to add entries to the |  | ||||||
|             losses during training. |  | ||||||
|         labels_discard (Iterable[str]): NER labels that will automatically get a "NIL" prediction. |  | ||||||
|         n_sents (int): The number of neighbouring sentences to take into account. |  | ||||||
|         incl_prior (bool): Whether or not to include prior probabilities from the KB in the model. |  | ||||||
|         incl_context (bool): Whether or not to include the local context in the model. |  | ||||||
|         entity_vector_length (int): Size of encoding vectors in the KB. |  | ||||||
|         get_candidates (Callable[[KnowledgeBase, Span], Iterable[Candidate]]): Function that |  | ||||||
|             produces a list of candidates, given a certain knowledge base and a textual mention. |  | ||||||
|         scorer (Optional[Callable]): The scoring method. Defaults to Scorer.score_links. |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#init |  | ||||||
|         """ |  | ||||||
|         self.vocab = vocab |  | ||||||
|         self.model = model |  | ||||||
|         self.name = name |  | ||||||
|         self.labels_discard = list(labels_discard) |  | ||||||
|         self.n_sents = n_sents |  | ||||||
|         self.incl_prior = incl_prior |  | ||||||
|         self.incl_context = incl_context |  | ||||||
|         self.get_candidates = get_candidates |  | ||||||
|         self.cfg: Dict[str, Any] = {"overwrite": overwrite} |  | ||||||
|         self.distance = CosineDistance(normalize=False) |  | ||||||
|         # how many neighbour sentences to take into account |  | ||||||
|         # create an empty KB by default. If you want to load a predefined one, specify it in 'initialize'. |  | ||||||
|         self.kb = empty_kb(entity_vector_length)(self.vocab) |  | ||||||
|         self.scorer = scorer |  | ||||||
| 
 |  | ||||||
|     def set_kb(self, kb_loader: Callable[[Vocab], KnowledgeBase]): |  | ||||||
|         """Define the KB of this pipe by providing a function that will |  | ||||||
|         create it using this object's vocab.""" |  | ||||||
|         if not callable(kb_loader): |  | ||||||
|             raise ValueError(Errors.E885.format(arg_type=type(kb_loader))) |  | ||||||
| 
 |  | ||||||
|         self.kb = kb_loader(self.vocab) |  | ||||||
| 
 |  | ||||||
|     def validate_kb(self) -> None: |  | ||||||
|         # Raise an error if the knowledge base is not initialized. |  | ||||||
|         if self.kb is None: |  | ||||||
|             raise ValueError(Errors.E1018.format(name=self.name)) |  | ||||||
|         if len(self.kb) == 0: |  | ||||||
|             raise ValueError(Errors.E139.format(name=self.name)) |  | ||||||
| 
 |  | ||||||
|     def initialize( |  | ||||||
|         self, |  | ||||||
|         get_examples: Callable[[], Iterable[Example]], |  | ||||||
|         *, |  | ||||||
|         nlp: Optional[Language] = None, |  | ||||||
|         kb_loader: Optional[Callable[[Vocab], KnowledgeBase]] = None, |  | ||||||
|     ): |  | ||||||
|         """Initialize the pipe for training, using a representative set |  | ||||||
|         of data examples. |  | ||||||
| 
 |  | ||||||
|         get_examples (Callable[[], Iterable[Example]]): Function that |  | ||||||
|             returns a representative sample of gold-standard Example objects. |  | ||||||
|         nlp (Language): The current nlp object the component is part of. |  | ||||||
|         kb_loader (Callable[[Vocab], KnowledgeBase]): A function that creates an InMemoryLookupKB from a Vocab instance. |  | ||||||
|             Note that providing this argument, will overwrite all data accumulated in the current KB. |  | ||||||
|             Use this only when loading a KB as-such from file. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#initialize |  | ||||||
|         """ |  | ||||||
|         validate_get_examples(get_examples, "EntityLinker_v1.initialize") |  | ||||||
|         if kb_loader is not None: |  | ||||||
|             self.set_kb(kb_loader) |  | ||||||
|         self.validate_kb() |  | ||||||
|         nO = self.kb.entity_vector_length |  | ||||||
|         doc_sample = [] |  | ||||||
|         vector_sample = [] |  | ||||||
|         for example in islice(get_examples(), 10): |  | ||||||
|             doc_sample.append(example.x) |  | ||||||
|             vector_sample.append(self.model.ops.alloc1f(nO)) |  | ||||||
|         assert len(doc_sample) > 0, Errors.E923.format(name=self.name) |  | ||||||
|         assert len(vector_sample) > 0, Errors.E923.format(name=self.name) |  | ||||||
|         self.model.initialize( |  | ||||||
|             X=doc_sample, Y=self.model.ops.asarray(vector_sample, dtype="float32") |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def update( |  | ||||||
|         self, |  | ||||||
|         examples: Iterable[Example], |  | ||||||
|         *, |  | ||||||
|         drop: float = 0.0, |  | ||||||
|         sgd: Optional[Optimizer] = None, |  | ||||||
|         losses: Optional[Dict[str, float]] = None, |  | ||||||
|     ) -> Dict[str, float]: |  | ||||||
|         """Learn from a batch of documents and gold-standard information, |  | ||||||
|         updating the pipe's model. Delegates to predict and get_loss. |  | ||||||
| 
 |  | ||||||
|         examples (Iterable[Example]): A batch of Example objects. |  | ||||||
|         drop (float): The dropout rate. |  | ||||||
|         sgd (thinc.api.Optimizer): The optimizer. |  | ||||||
|         losses (Dict[str, float]): Optional record of the loss during training. |  | ||||||
|             Updated using the component name as the key. |  | ||||||
|         RETURNS (Dict[str, float]): The updated losses dictionary. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#update |  | ||||||
|         """ |  | ||||||
|         self.validate_kb() |  | ||||||
|         if losses is None: |  | ||||||
|             losses = {} |  | ||||||
|         losses.setdefault(self.name, 0.0) |  | ||||||
|         if not examples: |  | ||||||
|             return losses |  | ||||||
|         validate_examples(examples, "EntityLinker_v1.update") |  | ||||||
|         sentence_docs = [] |  | ||||||
|         for eg in examples: |  | ||||||
|             sentences = [s for s in eg.reference.sents] |  | ||||||
|             kb_ids = eg.get_aligned("ENT_KB_ID", as_string=True) |  | ||||||
|             for ent in eg.reference.ents: |  | ||||||
|                 # KB ID of the first token is the same as the whole span |  | ||||||
|                 kb_id = kb_ids[ent.start] |  | ||||||
|                 if kb_id: |  | ||||||
|                     try: |  | ||||||
|                         # find the sentence in the list of sentences. |  | ||||||
|                         sent_index = sentences.index(ent.sent) |  | ||||||
|                     except AttributeError: |  | ||||||
|                         # Catch the exception when ent.sent is None and provide a user-friendly warning |  | ||||||
|                         raise RuntimeError(Errors.E030) from None |  | ||||||
|                     # get n previous sentences, if there are any |  | ||||||
|                     start_sentence = max(0, sent_index - self.n_sents) |  | ||||||
|                     # get n posterior sentences, or as many < n as there are |  | ||||||
|                     end_sentence = min(len(sentences) - 1, sent_index + self.n_sents) |  | ||||||
|                     # get token positions |  | ||||||
|                     start_token = sentences[start_sentence].start |  | ||||||
|                     end_token = sentences[end_sentence].end |  | ||||||
|                     # append that span as a doc to training |  | ||||||
|                     sent_doc = eg.predicted[start_token:end_token].as_doc() |  | ||||||
|                     sentence_docs.append(sent_doc) |  | ||||||
|         set_dropout_rate(self.model, drop) |  | ||||||
|         if not sentence_docs: |  | ||||||
|             warnings.warn(Warnings.W093.format(name="Entity Linker")) |  | ||||||
|             return losses |  | ||||||
|         sentence_encodings, bp_context = self.model.begin_update(sentence_docs) |  | ||||||
|         loss, d_scores = self.get_loss( |  | ||||||
|             sentence_encodings=sentence_encodings, examples=examples |  | ||||||
|         ) |  | ||||||
|         bp_context(d_scores) |  | ||||||
|         if sgd is not None: |  | ||||||
|             self.finish_update(sgd) |  | ||||||
|         losses[self.name] += loss |  | ||||||
|         return losses |  | ||||||
| 
 |  | ||||||
|     def get_loss(self, examples: Iterable[Example], sentence_encodings: Floats2d): |  | ||||||
|         validate_examples(examples, "EntityLinker_v1.get_loss") |  | ||||||
|         entity_encodings = [] |  | ||||||
|         for eg in examples: |  | ||||||
|             kb_ids = eg.get_aligned("ENT_KB_ID", as_string=True) |  | ||||||
|             for ent in eg.reference.ents: |  | ||||||
|                 kb_id = kb_ids[ent.start] |  | ||||||
|                 if kb_id: |  | ||||||
|                     entity_encoding = self.kb.get_vector(kb_id) |  | ||||||
|                     entity_encodings.append(entity_encoding) |  | ||||||
|         entity_encodings = self.model.ops.asarray2f(entity_encodings) |  | ||||||
|         if sentence_encodings.shape != entity_encodings.shape: |  | ||||||
|             err = Errors.E147.format( |  | ||||||
|                 method="get_loss", msg="gold entities do not match up" |  | ||||||
|             ) |  | ||||||
|             raise RuntimeError(err) |  | ||||||
|         gradients = self.distance.get_grad(sentence_encodings, entity_encodings) |  | ||||||
|         loss = self.distance.get_loss(sentence_encodings, entity_encodings) |  | ||||||
|         loss = loss / len(entity_encodings) |  | ||||||
|         return float(loss), gradients |  | ||||||
| 
 |  | ||||||
|     def predict(self, docs: Iterable[Doc]) -> List[str]: |  | ||||||
|         """Apply the pipeline's model to a batch of docs, without modifying them. |  | ||||||
|         Returns the KB IDs for each entity in each doc, including NIL if there is |  | ||||||
|         no prediction. |  | ||||||
| 
 |  | ||||||
|         docs (Iterable[Doc]): The documents to predict. |  | ||||||
|         RETURNS (List[str]): The models prediction for each document. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#predict |  | ||||||
|         """ |  | ||||||
|         self.validate_kb() |  | ||||||
|         entity_count = 0 |  | ||||||
|         final_kb_ids: List[str] = [] |  | ||||||
|         if not docs: |  | ||||||
|             return final_kb_ids |  | ||||||
|         if isinstance(docs, Doc): |  | ||||||
|             docs = [docs] |  | ||||||
|         for i, doc in enumerate(docs): |  | ||||||
|             sentences = [s for s in doc.sents] |  | ||||||
|             if len(doc) > 0: |  | ||||||
|                 # Looping through each entity (TODO: rewrite) |  | ||||||
|                 for ent in doc.ents: |  | ||||||
|                     sent = ent.sent |  | ||||||
|                     sent_index = sentences.index(sent) |  | ||||||
|                     assert sent_index >= 0 |  | ||||||
|                     # get n_neighbour sentences, clipped to the length of the document |  | ||||||
|                     start_sentence = max(0, sent_index - self.n_sents) |  | ||||||
|                     end_sentence = min(len(sentences) - 1, sent_index + self.n_sents) |  | ||||||
|                     start_token = sentences[start_sentence].start |  | ||||||
|                     end_token = sentences[end_sentence].end |  | ||||||
|                     sent_doc = doc[start_token:end_token].as_doc() |  | ||||||
|                     # currently, the context is the same for each entity in a sentence (should be refined) |  | ||||||
|                     xp = self.model.ops.xp |  | ||||||
|                     if self.incl_context: |  | ||||||
|                         sentence_encoding = self.model.predict([sent_doc])[0] |  | ||||||
|                         sentence_encoding_t = sentence_encoding.T |  | ||||||
|                         sentence_norm = xp.linalg.norm(sentence_encoding_t) |  | ||||||
|                     entity_count += 1 |  | ||||||
|                     if ent.label_ in self.labels_discard: |  | ||||||
|                         # ignoring this entity - setting to NIL |  | ||||||
|                         final_kb_ids.append(self.NIL) |  | ||||||
|                     else: |  | ||||||
|                         candidates = list(self.get_candidates(self.kb, ent)) |  | ||||||
|                         if not candidates: |  | ||||||
|                             # no prediction possible for this entity - setting to NIL |  | ||||||
|                             final_kb_ids.append(self.NIL) |  | ||||||
|                         elif len(candidates) == 1: |  | ||||||
|                             # shortcut for efficiency reasons: take the 1 candidate |  | ||||||
|                             final_kb_ids.append(candidates[0].entity_) |  | ||||||
|                         else: |  | ||||||
|                             random.shuffle(candidates) |  | ||||||
|                             # set all prior probabilities to 0 if incl_prior=False |  | ||||||
|                             prior_probs = xp.asarray([c.prior_prob for c in candidates]) |  | ||||||
|                             if not self.incl_prior: |  | ||||||
|                                 prior_probs = xp.asarray([0.0 for _ in candidates]) |  | ||||||
|                             scores = prior_probs |  | ||||||
|                             # add in similarity from the context |  | ||||||
|                             if self.incl_context: |  | ||||||
|                                 entity_encodings = xp.asarray( |  | ||||||
|                                     [c.entity_vector for c in candidates] |  | ||||||
|                                 ) |  | ||||||
|                                 entity_norm = xp.linalg.norm(entity_encodings, axis=1) |  | ||||||
|                                 if len(entity_encodings) != len(prior_probs): |  | ||||||
|                                     raise RuntimeError( |  | ||||||
|                                         Errors.E147.format( |  | ||||||
|                                             method="predict", |  | ||||||
|                                             msg="vectors not of equal length", |  | ||||||
|                                         ) |  | ||||||
|                                     ) |  | ||||||
|                                 # cosine similarity |  | ||||||
|                                 sims = xp.dot(entity_encodings, sentence_encoding_t) / ( |  | ||||||
|                                     sentence_norm * entity_norm |  | ||||||
|                                 ) |  | ||||||
|                                 if sims.shape != prior_probs.shape: |  | ||||||
|                                     raise ValueError(Errors.E161) |  | ||||||
|                                 scores = prior_probs + sims - (prior_probs * sims) |  | ||||||
|                             best_index = scores.argmax().item() |  | ||||||
|                             best_candidate = candidates[best_index] |  | ||||||
|                             final_kb_ids.append(best_candidate.entity_) |  | ||||||
|         if not (len(final_kb_ids) == entity_count): |  | ||||||
|             err = Errors.E147.format( |  | ||||||
|                 method="predict", msg="result variables not of equal length" |  | ||||||
|             ) |  | ||||||
|             raise RuntimeError(err) |  | ||||||
|         return final_kb_ids |  | ||||||
| 
 |  | ||||||
|     def set_annotations(self, docs: Iterable[Doc], kb_ids: List[str]) -> None: |  | ||||||
|         """Modify a batch of documents, using pre-computed scores. |  | ||||||
| 
 |  | ||||||
|         docs (Iterable[Doc]): The documents to modify. |  | ||||||
|         kb_ids (List[str]): The IDs to set, produced by EntityLinker.predict. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#set_annotations |  | ||||||
|         """ |  | ||||||
|         count_ents = len([ent for doc in docs for ent in doc.ents]) |  | ||||||
|         if count_ents != len(kb_ids): |  | ||||||
|             raise ValueError(Errors.E148.format(ents=count_ents, ids=len(kb_ids))) |  | ||||||
|         i = 0 |  | ||||||
|         overwrite = self.cfg["overwrite"] |  | ||||||
|         for doc in docs: |  | ||||||
|             for ent in doc.ents: |  | ||||||
|                 kb_id = kb_ids[i] |  | ||||||
|                 i += 1 |  | ||||||
|                 for token in ent: |  | ||||||
|                     if token.ent_kb_id == 0 or overwrite: |  | ||||||
|                         token.ent_kb_id_ = kb_id |  | ||||||
| 
 |  | ||||||
|     def to_bytes(self, *, exclude=tuple()): |  | ||||||
|         """Serialize the pipe to a bytestring. |  | ||||||
| 
 |  | ||||||
|         exclude (Iterable[str]): String names of serialization fields to exclude. |  | ||||||
|         RETURNS (bytes): The serialized object. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#to_bytes |  | ||||||
|         """ |  | ||||||
|         self._validate_serialization_attrs() |  | ||||||
|         serialize = {} |  | ||||||
|         if hasattr(self, "cfg") and self.cfg is not None: |  | ||||||
|             serialize["cfg"] = lambda: srsly.json_dumps(self.cfg) |  | ||||||
|         serialize["vocab"] = lambda: self.vocab.to_bytes(exclude=exclude) |  | ||||||
|         serialize["kb"] = self.kb.to_bytes |  | ||||||
|         serialize["model"] = self.model.to_bytes |  | ||||||
|         return util.to_bytes(serialize, exclude) |  | ||||||
| 
 |  | ||||||
|     def from_bytes(self, bytes_data, *, exclude=tuple()): |  | ||||||
|         """Load the pipe from a bytestring. |  | ||||||
| 
 |  | ||||||
|         exclude (Iterable[str]): String names of serialization fields to exclude. |  | ||||||
|         RETURNS (TrainablePipe): The loaded object. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#from_bytes |  | ||||||
|         """ |  | ||||||
|         self._validate_serialization_attrs() |  | ||||||
| 
 |  | ||||||
|         def load_model(b): |  | ||||||
|             try: |  | ||||||
|                 self.model.from_bytes(b) |  | ||||||
|             except AttributeError: |  | ||||||
|                 raise ValueError(Errors.E149) from None |  | ||||||
| 
 |  | ||||||
|         deserialize = {} |  | ||||||
|         if hasattr(self, "cfg") and self.cfg is not None: |  | ||||||
|             deserialize["cfg"] = lambda b: self.cfg.update(srsly.json_loads(b)) |  | ||||||
|         deserialize["vocab"] = lambda b: self.vocab.from_bytes(b, exclude=exclude) |  | ||||||
|         deserialize["kb"] = lambda b: self.kb.from_bytes(b) |  | ||||||
|         deserialize["model"] = load_model |  | ||||||
|         util.from_bytes(bytes_data, deserialize, exclude) |  | ||||||
|         return self |  | ||||||
| 
 |  | ||||||
|     def to_disk( |  | ||||||
|         self, path: Union[str, Path], *, exclude: Iterable[str] = SimpleFrozenList() |  | ||||||
|     ) -> None: |  | ||||||
|         """Serialize the pipe to disk. |  | ||||||
| 
 |  | ||||||
|         path (str / Path): Path to a directory. |  | ||||||
|         exclude (Iterable[str]): String names of serialization fields to exclude. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#to_disk |  | ||||||
|         """ |  | ||||||
|         serialize = {} |  | ||||||
|         serialize["vocab"] = lambda p: self.vocab.to_disk(p, exclude=exclude) |  | ||||||
|         serialize["cfg"] = lambda p: srsly.write_json(p, self.cfg) |  | ||||||
|         serialize["kb"] = lambda p: self.kb.to_disk(p) |  | ||||||
|         serialize["model"] = lambda p: self.model.to_disk(p) |  | ||||||
|         util.to_disk(path, serialize, exclude) |  | ||||||
| 
 |  | ||||||
|     def from_disk( |  | ||||||
|         self, path: Union[str, Path], *, exclude: Iterable[str] = SimpleFrozenList() |  | ||||||
|     ) -> "EntityLinker_v1": |  | ||||||
|         """Load the pipe from disk. Modifies the object in place and returns it. |  | ||||||
| 
 |  | ||||||
|         path (str / Path): Path to a directory. |  | ||||||
|         exclude (Iterable[str]): String names of serialization fields to exclude. |  | ||||||
|         RETURNS (EntityLinker): The modified EntityLinker object. |  | ||||||
| 
 |  | ||||||
|         DOCS: https://spacy.io/api/entitylinker#from_disk |  | ||||||
|         """ |  | ||||||
| 
 |  | ||||||
|         def load_model(p): |  | ||||||
|             try: |  | ||||||
|                 with p.open("rb") as infile: |  | ||||||
|                     self.model.from_bytes(infile.read()) |  | ||||||
|             except AttributeError: |  | ||||||
|                 raise ValueError(Errors.E149) from None |  | ||||||
| 
 |  | ||||||
|         deserialize: Dict[str, Callable[[Any], Any]] = {} |  | ||||||
|         deserialize["cfg"] = lambda p: self.cfg.update(deserialize_config(p)) |  | ||||||
|         deserialize["vocab"] = lambda p: self.vocab.from_disk(p, exclude=exclude) |  | ||||||
|         deserialize["kb"] = lambda p: self.kb.from_disk(p) |  | ||||||
|         deserialize["model"] = load_model |  | ||||||
|         util.from_disk(path, deserialize, exclude) |  | ||||||
|         return self |  | ||||||
| 
 |  | ||||||
|     def rehearse(self, examples, *, sgd=None, losses=None, **config): |  | ||||||
|         raise NotImplementedError |  | ||||||
| 
 |  | ||||||
|     def add_label(self, label): |  | ||||||
|         raise NotImplementedError |  | ||||||
|  | @ -12,7 +12,6 @@ from spacy.lang.en import English | ||||||
| from spacy.ml import load_kb | from spacy.ml import load_kb | ||||||
| from spacy.ml.models.entity_linker import build_span_maker | from spacy.ml.models.entity_linker import build_span_maker | ||||||
| from spacy.pipeline import EntityLinker, TrainablePipe | from spacy.pipeline import EntityLinker, TrainablePipe | ||||||
| from spacy.pipeline.legacy import EntityLinker_v1 |  | ||||||
| from spacy.pipeline.tok2vec import DEFAULT_TOK2VEC_MODEL | from spacy.pipeline.tok2vec import DEFAULT_TOK2VEC_MODEL | ||||||
| from spacy.scorer import Scorer | from spacy.scorer import Scorer | ||||||
| from spacy.tests.util import make_tempdir | from spacy.tests.util import make_tempdir | ||||||
|  | @ -997,6 +996,8 @@ def test_scorer_links(): | ||||||
| ) | ) | ||||||
| # fmt: on | # fmt: on | ||||||
| def test_legacy_architectures(name, config): | def test_legacy_architectures(name, config): | ||||||
|  |     from spacy_legacy.components.entity_linker import EntityLinker_v1 | ||||||
|  | 
 | ||||||
|     # Ensure that the legacy architectures still work |     # Ensure that the legacy architectures still work | ||||||
|     vector_length = 3 |     vector_length = 3 | ||||||
|     nlp = English() |     nlp = English() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user