|
import argparse |
|
import html |
|
import time |
|
|
|
from extend import spacy_component |
|
|
|
import spacy |
|
import streamlit as st |
|
from annotated_text import annotation |
|
from classy.utils.streamlit import get_md_200_random_color_generator |
|
|
|
|
|
def main( |
|
model_checkpoint_path: str, |
|
inventory_path: str, |
|
cuda_device: int, |
|
): |
|
|
|
examples = [ |
|
"Japan began the defence of their title with a lucky 2-1 win against Syria in a championship match on Friday.", |
|
"The project was coded in Java.", |
|
"Rome is in Italy", |
|
] |
|
|
|
|
|
|
|
@st.cache(allow_output_mutation=True) |
|
def load_resources(inventory_path): |
|
|
|
|
|
nlp = spacy.load("en_core_web_sm") |
|
extend_config = dict( |
|
checkpoint_path=model_checkpoint_path, |
|
mentions_inventory_path=inventory_path, |
|
device=cuda_device, |
|
tokens_per_batch=10_000, |
|
) |
|
nlp.add_pipe("extend", after="ner", config=extend_config) |
|
|
|
|
|
nlp(examples[0]) |
|
|
|
|
|
return nlp |
|
|
|
|
|
load_resources(inventory_path) |
|
|
|
|
|
st.write( |
|
""" |
|
<style type="text/css"> |
|
a { |
|
text-decoration: none !important; |
|
} |
|
</style> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
|
|
|
|
st.markdown( |
|
"<h1 style='text-align: center;'>ExtEnD: Extractive Entity Disambiguation</h1>", |
|
unsafe_allow_html=True, |
|
) |
|
st.write( |
|
""" |
|
<div align="center"> |
|
<a href="https://sunglasses-ai.github.io/classy/"> |
|
<img alt="Python" style="height: 3em; margin: 0em 1em 2em 1em;" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxpdmVsbG9fMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHdpZHRoPSI0MzcuNnB4IiBoZWlnaHQ9IjQxMy45NzhweCIgdmlld0JveD0iMCAwIDQzNy42IDQxMy45NzgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQzNy42IDQxMy45NzgiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZmlsbD0iI0ZGQ0MwMCIgZD0iTTM5NC42NjgsMTczLjAzOGMtMS4wMTgsMC0yLjAyLDAuMDY1LTMuMDE1LDAuMTUyQzM3NS42NjUsODIuODExLDI5Ni43OSwxNC4xNDYsMjAxLjgyMywxNC4xNDYNCgkJQzk1LjMxOSwxNC4xNDYsOC45OCwxMDAuNDg1LDguOTgsMjA2Ljk4OWMwLDEwNi41MDUsODYuMzM5LDE5Mi44NDMsMTkyLjg0MywxOTIuODQzYzk0Ljk2NywwLDE3My44NDItNjguNjY1LDE4OS44MjktMTU5LjA0NA0KCQljMC45OTUsMC4wODcsMS45OTcsMC4xNTIsMy4wMTUsMC4xNTJjMTguNzUxLDAsMzMuOTUyLTE1LjIsMzMuOTUyLTMzLjk1MkM0MjguNjIsMTg4LjIzOSw0MTMuNDE5LDE3My4wMzgsMzk0LjY2OCwxNzMuMDM4eg0KCQkgTTIwMS44MjMsMzQ2Ljg2OWMtNzcuMTMsMC0xMzkuODgtNjIuNzUtMTM5Ljg4LTEzOS44OGMwLTc3LjEyOSw2Mi43NS0xMzkuODc5LDEzOS44OC0xMzkuODc5czEzOS44OCw2Mi43NSwxMzkuODgsMTM5Ljg3OQ0KCQlDMzQxLjcwMywyODQuMTE5LDI3OC45NTMsMzQ2Ljg2OSwyMDEuODIzLDM0Ni44Njl6Ii8+DQoJPGc+DQoJCTxwYXRoIGZpbGw9IiNGRkNDMDAiIGQ9Ik0xMTQuOTA3LDIzMy40NzNjLTE0LjYyNiwwLTI2LjQ4My0xMS44NTYtMjYuNDgzLTI2LjQ4M2MwLTYyLjUyOCw1MC44NzEtMTEzLjQwMiwxMTMuMzk4LTExMy40MDINCgkJCWMxNC42MjYsMCwyNi40ODMsMTEuODU2LDI2LjQ4MywyNi40ODNzLTExLjg1NiwyNi40ODMtMjYuNDgzLDI2LjQ4M2MtMzMuMzI0LDAtNjAuNDMzLDI3LjExMi02MC40MzMsNjAuNDM2DQoJCQlDMTQxLjM5LDIyMS42MTcsMTI5LjUzNCwyMzMuNDczLDExNC45MDcsMjMzLjQ3M3oiLz4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4NCg=="> |
|
</a> |
|
<a href="https://spacy.io/" tyle="text-decoration: none"> |
|
<img alt="spaCy" style="height: 3em; margin: 0em 1em 2em 1em;" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgOTAwIDUwMCAxNzUiIHdpZHRoPSIxNTAiIGhlaWdodD0iNTMiPjxwYXRoIGZpbGw9IiMwOUEzRDUiIGQ9Ik02NC44IDk3MC42Yy0xMS4zLTEuMy0xMi4yLTE2LjUtMjYuNy0xNS4yLTcgMC0xMy42IDIuOS0xMy42IDkuNCAwIDkuNyAxNSAxMC42IDI0LjEgMTMuMSAxNS40IDQuNyAzMC40IDcuOSAzMC40IDI0LjcgMCAyMS4zLTE2LjcgMjguNy0zOC43IDI4LjctMTguNCAwLTM3LjEtNi41LTM3LjEtMjMuNSAwLTQuNyA0LjUtOC40IDguOS04LjQgNS41IDAgNy41IDIuMyA5LjQgNi4yIDQuMyA3LjUgOS4xIDExLjYgMjEgMTEuNiA3LjUgMCAxNS4zLTIuOSAxNS4zLTkuNCAwLTkuMy05LjUtMTEuMy0xOS4zLTEzLjYtMTcuNC00LjktMzIuMy03LjQtMzQtMjYuNy0xLjgtMzIuOSA2Ni43LTM0LjEgNzAuNi01LjMtLjMgNS4yLTUuMiA4LjQtMTAuMyA4LjR6bTgxLjUtMjguOGMyNC4xIDAgMzcuNyAyMC4xIDM3LjcgNDQuOSAwIDI0LjktMTMuMiA0NC45LTM3LjcgNDQuOS0xMy42IDAtMjIuMS01LjgtMjguMi0xNC43djMyLjljMCA5LjktMy4yIDE0LjctMTAuNCAxNC43LTguOCAwLTEwLjQtNS42LTEwLjQtMTQuN3YtOTUuNmMwLTcuOCAzLjMtMTIuNiAxMC40LTEyLjYgNi43IDAgMTAuNCA1LjMgMTAuNCAxMi42djIuN2M2LjgtOC41IDE0LjYtMTUuMSAyOC4yLTE1LjF6bS01LjcgNzIuOGMxNC4xIDAgMjAuNC0xMyAyMC40LTI4LjIgMC0xNC44LTYuNC0yOC4yLTIwLjQtMjguMi0xNC43IDAtMjEuNSAxMi4xLTIxLjUgMjguMi4xIDE1LjcgNi45IDI4LjIgMjEuNSAyOC4yem01OS44LTQ5LjNjMC0xNy4zIDE5LjktMjMuNSAzOS4yLTIzLjUgMjcuMSAwIDM4LjIgNy45IDM4LjIgMzR2MjUuMmMwIDYgMy43IDE3LjkgMy43IDIxLjUgMCA1LjUtNSA4LjktMTAuNCA4LjktNiAwLTEwLjQtNy0xMy42LTEyLjEtOC44IDctMTguMSAxMi4xLTMyLjQgMTIuMS0xNS44IDAtMjguMi05LjMtMjguMi0yNC43IDAtMTMuNiA5LjctMjEuNCAyMS41LTI0LjEgMCAuMSAzNy43LTguOSAzNy43LTkgMC0xMS42LTQuMS0xNi43LTE2LjMtMTYuNy0xMC43IDAtMTYuMiAyLjktMjAuNCA5LjQtMy40IDQuOS0yLjkgNy44LTkuNCA3LjgtNS4xIDAtOS42LTMuNi05LjYtOC44em0zMi4yIDUxLjljMTYuNSAwIDIzLjUtOC43IDIzLjUtMjYuMXYtMy43Yy00LjQgMS41LTIyLjQgNi0yNy4zIDYuNy01LjIgMS0xMC40IDQuOS0xMC40IDExIC4yIDYuNyA3LjEgMTIuMSAxNC4yIDEyLjF6TTM1NCA5MDljMjMuMyAwIDQ4LjYgMTMuOSA0OC42IDM2LjEgMCA1LjctNC4zIDEwLjQtOS45IDEwLjQtNy42IDAtOC43LTQuMS0xMi4xLTkuOS01LjYtMTAuMy0xMi4yLTE3LjItMjYuNy0xNy4yLTIyLjMtLjItMzIuMyAxOS0zMi4zIDQyLjggMCAyNCA4LjMgNDEuMyAzMS40IDQxLjMgMTUuMyAwIDIzLjgtOC45IDI4LjItMjAuNCAxLjgtNS4zIDQuOS0xMC40IDExLjYtMTAuNCA1LjIgMCAxMC40IDUuMyAxMC40IDExIDAgMjMuNS0yNCAzOS43LTQ4LjYgMzkuNy0yNyAwLTQyLjMtMTEuNC01MC42LTMwLjQtNC4xLTkuMS02LjctMTguNC02LjctMzEuNC0uNC0zNi40IDIwLjgtNjEuNiA1Ni43LTYxLjZ6bTEzMy4zIDMyLjhjNiAwIDkuNCAzLjkgOS40IDkuOSAwIDIuNC0xLjkgNy4zLTIuNyA5LjlsLTI4LjcgNzUuNGMtNi40IDE2LjQtMTEuMiAyNy43LTMyLjkgMjcuNy0xMC4zIDAtMTkuMy0uOS0xOS4zLTkuOSAwLTUuMiAzLjktNy44IDkuNC03LjggMSAwIDIuNy41IDMuNy41IDEuNiAwIDIuNy41IDMuNy41IDEwLjkgMCAxMi40LTExLjIgMTYuMy0xOC45bC0yNy43LTY4LjVjLTEuNi0zLjctMi43LTYuMi0yLjctOC40IDAtNiA0LjctMTAuNCAxMS0xMC40IDcgMCA5LjggNS41IDExLjYgMTEuNmwxOC4zIDU0LjMgMTguMy01MC4yYzIuNy03LjggMy0xNS43IDEyLjMtMTUuN3oiIC8+IDwvc3ZnPg=="> |
|
</a> |
|
</div> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
|
|
|
|
def hiw(): |
|
st.markdown(""" |
|
## How it works |
|
|
|
ExtEnD frames Entity Disambiguation as a text extraction problem: |
|
""") |
|
st.image( |
|
"data/repo-assets/extend_formulation.png", caption="ExtEnD Formulation" |
|
) |
|
st.markdown( |
|
""" |
|
Given the sentence *After a long fight Superman saved Metropolis*, where *Superman* is the mention |
|
to disambiguate, ExtEnD first concatenates the descriptions of all the possible candidates of *Superman* in the |
|
inventory and then selects the span whose description best suits the mention in its context. |
|
|
|
To use ExtEnD for full end2end entity linking, as we do in *Demo*, we just need to leverage a mention |
|
identifier. Here [we use spaCy](https://github.com/SapienzaNLP/extend#spacy) (more specifically, its NER) and run ExtEnD on each named |
|
entity spaCy identifies (if the corresponding mention is contained in the inventory). |
|
|
|
##### Links: |
|
* [Full Paper](https://www.researchgate.net/publication/359392427_ExtEnD_Extractive_Entity_Disambiguation) |
|
* [GitHub](https://github.com/SapienzaNLP/extend) |
|
""" |
|
) |
|
|
|
|
|
def demo(): |
|
st.markdown("## Demo") |
|
|
|
|
|
placeholder = st.selectbox( |
|
"Examples", |
|
options=examples, |
|
index=0, |
|
) |
|
input_text = st.text_area("Input text to entity-disambiguate", placeholder) |
|
|
|
|
|
should_disambiguate = st.button("Disambiguate", key="classify") |
|
|
|
|
|
nlp = load_resources(inventory_path) |
|
color_generator = get_md_200_random_color_generator() |
|
|
|
if should_disambiguate: |
|
|
|
|
|
time_start = time.perf_counter() |
|
doc = nlp(input_text) |
|
time_end = time.perf_counter() |
|
|
|
|
|
entities = {} |
|
for ent in doc.ents: |
|
if ent._.disambiguated_entity is not None: |
|
entities[ent.start_char] = ( |
|
ent.start_char, |
|
ent.end_char, |
|
ent.text, |
|
ent._.disambiguated_entity, |
|
) |
|
|
|
|
|
|
|
annotated_html_components = [] |
|
|
|
assert all(any(t.idx == _s for t in doc) for _s in entities) |
|
it = iter(list(doc)) |
|
while True: |
|
try: |
|
t = next(it) |
|
except StopIteration: |
|
break |
|
if t.idx in entities: |
|
_start, _end, _text, _entity = entities[t.idx] |
|
while t.idx + len(t) != _end: |
|
t = next(it) |
|
annotated_html_components.append( |
|
f"<a href=\"https://en.wikipedia.org/wiki/{_entity.lower().replace(' ', '_').capitalize()}\">{annotation(*(_text, _entity, color_generator()))}</a>" |
|
) |
|
else: |
|
annotated_html_components.append(str(html.escape(t.text))) |
|
|
|
st.markdown( |
|
"\n".join( |
|
[ |
|
"<div>", |
|
*annotated_html_components, |
|
"<p></p>" |
|
f'<div style="text-align: right"><p style="color: gray">Time: {(time_end - time_start):.2f}s</p></div>' |
|
"</div>", |
|
] |
|
), |
|
unsafe_allow_html=True, |
|
) |
|
|
|
demo() |
|
hiw() |
|
|
|
|
|
if __name__ == "__main__": |
|
main( |
|
"experiments/extend-longformer-large/2021-10-22/09-11-39/checkpoints/best.ckpt", |
|
"data/inventories/le-and-titov-2018-inventory.min-count-2.sqlite3", |
|
cuda_device=-1, |
|
) |
|
|