|
import streamlit as st |
|
import time |
|
import pandas as pd |
|
import io |
|
from streamlit_extras.stylable_container import stylable_container |
|
import plotly.express as px |
|
import zipfile |
|
from gliner import GLiNER |
|
import os |
|
from comet_ml import Experiment |
|
|
|
|
|
st.set_page_config(layout="wide", page_title="Named Entity Recognition App") |
|
|
|
|
|
st.subheader("Free NER Web App", divider="red") |
|
st.link_button("DEMO APP by nlpblogs", "https://nlpblogs.com", type="tertiary") |
|
|
|
expander = st.expander("**Important notes on the Free NER Web App**") |
|
expander.write(''' |
|
**Named Entities:** This Free NER Web App predicts eight (8) labels |
|
grouped into three categories: **People** (person, organization, position), |
|
**Locations** (country, city), and **Numbers** (date, money, percent value). |
|
Results are presented in an easy-to-read table, visualized in an |
|
interactive treemap, pie chart, and bar chart, and are available for download |
|
along with a Glossary of tags. |
|
|
|
**How to Use:** Type or paste your text and press Ctrl + Enter. Then, |
|
click the 'Results' button to extract and tag entities in your text data. |
|
|
|
**Usage Limits:** Unlimited number of Result requests. |
|
|
|
**Supported Languages:** English |
|
|
|
**Customization:** To change the app's background color to white or |
|
black, click the three-dot menu on the right-hand side of your app, go to |
|
Settings and then Choose app theme, colors and fonts. |
|
|
|
**Technical issues:** If your connection times out, please refresh the |
|
page or reopen the app's URL. |
|
|
|
For any errors or inquiries, please contact us at [email protected] |
|
''') |
|
|
|
|
|
with st.sidebar: |
|
container = st.container(border=True) |
|
container.write("**Named Entity Recognition (NER)** is the task of extracting and tagging entities in text data. Entities can be persons, organizations, locations, countries, products, events etc.") |
|
st.subheader("Build your own NER Web App in a minute without writing a single line of code.", divider="red") |
|
st.link_button("NER File Builder", "https://nlpblogs.com/shop/named-entity-recognition-ner/ner-file-builder/", type="primary") |
|
|
|
|
|
COMET_API_KEY = os.environ.get("COMET_API_KEY") |
|
COMET_WORKSPACE = os.environ.get("COMET_WORKSPACE") |
|
COMET_PROJECT_NAME = os.environ.get("COMET_PROJECT_NAME") |
|
|
|
if COMET_API_KEY and COMET_WORKSPACE and COMET_PROJECT_NAME: |
|
comet_initialized = True |
|
else: |
|
comet_initialized = False |
|
st.warning("Comet ML not initialized. Check environment variables.") |
|
|
|
|
|
@st.cache_resource |
|
def load_gliner_model(): |
|
"""Caches the GLiNER model to prevent re-loading on every app rerun.""" |
|
return GLiNER.from_pretrained("knowledgator/gliner-multitask-large-v0.5") |
|
|
|
|
|
model = load_gliner_model() |
|
|
|
|
|
|
|
text = st.text_area("Type or paste your text below, and then press Ctrl + Enter", key='my_text_area') |
|
st.write("**Input text**: ", text) |
|
|
|
def clear_text(): |
|
"""Clears the text area.""" |
|
st.session_state['my_text_area'] = "" |
|
|
|
st.button("Clear text", on_click=clear_text) |
|
st.divider() |
|
|
|
|
|
if st.button("Results"): |
|
start_time = time.time() |
|
if not text.strip(): |
|
st.warning("Please enter some text to extract entities.") |
|
else: |
|
with st.spinner("Extracting entities..."): |
|
|
|
|
|
labels = ["person", "country", "city", "organization", "date", "seconds", "money", "percent value", "position"] |
|
entities = model.predict_entities(text, labels) |
|
|
|
df = pd.DataFrame(entities) |
|
|
|
|
|
if not df.empty: |
|
|
|
category_mapping = { |
|
"person": "People", |
|
"organization": "People", |
|
"position": "People", |
|
"country": "Locations", |
|
"city": "Locations", |
|
"date": "Time", |
|
"seconds": "Time", |
|
"money": "Numbers", |
|
"percent value": "Numbers" |
|
} |
|
|
|
df['category'] = df['label'].map(category_mapping) |
|
|
|
if comet_initialized: |
|
experiment = Experiment( |
|
api_key=COMET_API_KEY, |
|
workspace=COMET_WORKSPACE, |
|
project_name=COMET_PROJECT_NAME, |
|
) |
|
experiment.log_parameter("input_text", text) |
|
experiment.log_table("predicted_entities", df) |
|
experiment.end() |
|
|
|
properties = {"border": "2px solid gray", "color": "blue", "font-size": "16px"} |
|
df_styled = df.style.set_properties(**properties) |
|
st.dataframe(df_styled) |
|
|
|
with st.expander("See Glossary of tags"): |
|
st.write(''' |
|
'**text**': ['entity extracted from your text data'] |
|
|
|
'**score**': ['accuracy score; how accurately a tag has been assigned to a given entity'] |
|
|
|
'**label**': ['label (tag) assigned to a given extracted entity'] |
|
|
|
'**category**': ['the high-level category for the label'] |
|
|
|
'**start**': ['index of the start of the corresponding entity'] |
|
|
|
'**end**': ['index of the end of the corresponding entity'] |
|
''') |
|
|
|
|
|
if not df.empty: |
|
st.subheader("Tree map", divider="red") |
|
|
|
fig = px.treemap(df, path=[px.Constant("all"), 'category', 'label', 'text'], values='score', color='category') |
|
fig.update_layout(margin=dict(t=50, l=25, r=25, b=25)) |
|
st.plotly_chart(fig) |
|
if comet_initialized: |
|
experiment.log_figure(figure=fig, figure_name="entity_treemap_categories") |
|
|
|
col1, col2 = st.columns(2) |
|
with col1: |
|
st.subheader("Pie Chart", divider="red") |
|
|
|
value_counts1 = df['category'].value_counts() |
|
df1 = pd.DataFrame(value_counts1) |
|
final_df = df1.reset_index().rename(columns={"index": "category"}) |
|
fig1 = px.pie(final_df, values='count', names='category', hover_data=['count'], labels={'count': 'count'}, title='Percentage of predicted categories') |
|
fig1.update_traces(textposition='inside', textinfo='percent+label') |
|
st.plotly_chart(fig1) |
|
if comet_initialized: |
|
experiment.log_figure(figure=fig1, figure_name="category_pie_chart") |
|
|
|
with col2: |
|
st.subheader("Bar Chart", divider="red") |
|
|
|
fig2 = px.bar(final_df, x="count", y="category", color="category", text_auto=True, title='Occurrences of predicted categories') |
|
st.plotly_chart(fig2) |
|
if comet_initialized: |
|
experiment.log_figure(figure=fig2, figure_name="category_bar_chart") |
|
else: |
|
st.info("No entities found in the provided text.") |
|
|
|
|
|
|
|
dfa = pd.DataFrame( |
|
data={ |
|
'Column Name': ['text', 'label', 'score', 'start', 'end', 'category'], |
|
'Description': [ |
|
'entity extracted from your text data', |
|
'label (tag) assigned to a given extracted entity', |
|
'accuracy score; how accurately a tag has been assigned to a given entity', |
|
'index of the start of the corresponding entity', |
|
'index of the end of the corresponding entity', |
|
'the broader category the entity belongs to', |
|
] |
|
} |
|
) |
|
|
|
buf = io.BytesIO() |
|
with zipfile.ZipFile(buf, "w") as myzip: |
|
myzip.writestr("Summary of the results.csv", df.to_csv(index=False)) |
|
myzip.writestr("Glossary of tags.csv", dfa.to_csv(index=False)) |
|
|
|
with stylable_container( |
|
key="download_button", |
|
css_styles="""button { background-color: yellow; border: 1px solid black; padding: 5px; color: black; }""", |
|
): |
|
st.download_button( |
|
label="Download zip file", |
|
data=buf.getvalue(), |
|
file_name="zip file.zip", |
|
mime="application/zip", |
|
) |
|
st.divider() |
|
|
|
end_time = time.time() |
|
elapsed_time = end_time - start_time |
|
st.info(f"Results processed in **{elapsed_time:.2f} seconds**.") |
|
|
|
|
|
|
|
|