|
|
import streamlit as st |
|
|
import torch |
|
|
from transformers import AutoConfig, AutoTokenizer, AutoModel |
|
|
from huggingface_hub import login |
|
|
import re |
|
|
import copy |
|
|
from modeling import ST2ModelV2 |
|
|
from huggingface_hub import hf_hub_download |
|
|
from safetensors.torch import load_file |
|
|
|
|
|
hf_token = st.secrets["HUGGINGFACE_TOKEN"] |
|
|
login(token=hf_token) |
|
|
|
|
|
|
|
|
@st.cache_resource |
|
|
def load_model(): |
|
|
|
|
|
config = AutoConfig.from_pretrained("roberta-large") |
|
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained("roberta-large", use_fast=True, add_prefix_space=True) |
|
|
|
|
|
class Args: |
|
|
def __init__(self): |
|
|
|
|
|
self.dropout = 0.3 |
|
|
self.signal_classification = True |
|
|
self.pretrained_signal_detector = False |
|
|
|
|
|
|
|
|
args = Args() |
|
|
|
|
|
model = ST2ModelV2(args) |
|
|
|
|
|
|
|
|
repo_id = "anamargarida/MultiSpanExtractionRoberta2" |
|
|
filename = "model.safetensors" |
|
|
|
|
|
|
|
|
model_path = hf_hub_download(repo_id=repo_id, filename=filename) |
|
|
|
|
|
state_dict = load_file(model_path) |
|
|
|
|
|
model.load_state_dict(state_dict) |
|
|
|
|
|
return tokenizer, model |
|
|
|
|
|
|
|
|
tokenizer, model = load_model() |
|
|
|
|
|
model.eval() |
|
|
def extract_arguments(text, tokenizer, model): |
|
|
|
|
|
class Args: |
|
|
def __init__(self): |
|
|
self.signal_classification = True |
|
|
self.pretrained_signal_detector = False |
|
|
|
|
|
args = Args() |
|
|
|
|
|
inputs = tokenizer(text, return_offsets_mapping=True, return_tensors="pt") |
|
|
|
|
|
word_ids = inputs.word_ids() |
|
|
|
|
|
with torch.no_grad(): |
|
|
outputs = model(**inputs) |
|
|
|
|
|
|
|
|
start_cause_logits = outputs["start_arg0_logits"].squeeze(0) |
|
|
end_cause_logits = outputs["end_arg0_logits"].squeeze(0) |
|
|
start_effect_logits = outputs["start_arg1_logits"].squeeze(0) |
|
|
end_effect_logits = outputs["end_arg1_logits"].squeeze(0) |
|
|
start_signal_logits = outputs["start_sig_logits"].squeeze(0) |
|
|
end_signal_logits = outputs["end_sig_logits"].squeeze(0) |
|
|
|
|
|
|
|
|
start_cause_all = [] |
|
|
end_cause_all = [] |
|
|
start_effect_all = [] |
|
|
end_effect_all = [] |
|
|
start_signal_all = [] |
|
|
end_signal_all = [] |
|
|
|
|
|
num_relations = 3 |
|
|
|
|
|
|
|
|
has_signal = 1 |
|
|
if args.signal_classification: |
|
|
if not args.pretrained_signal_detector: |
|
|
has_signal = outputs["signal_classification_logits"].argmax().item() |
|
|
else: |
|
|
has_signal = signal_detector.predict(text=batch["text"]) |
|
|
|
|
|
|
|
|
|
|
|
for rel_idx in range(num_relations): |
|
|
|
|
|
|
|
|
|
|
|
start_cause, end_cause, start_effect, end_effect = model.position_selector( |
|
|
start_cause_logits=start_cause_logits, |
|
|
end_cause_logits=end_cause_logits, |
|
|
start_effect_logits=start_effect_logits, |
|
|
end_effect_logits=end_effect_logits, |
|
|
word_ids=word_ids, |
|
|
rel_idx=rel_idx |
|
|
) |
|
|
|
|
|
start_cause_all.append(start_cause) |
|
|
end_cause_all.append(end_cause) |
|
|
start_effect_all.append(start_effect) |
|
|
end_effect_all.append(end_effect) |
|
|
|
|
|
|
|
|
|
|
|
if has_signal: |
|
|
start_signal, end_signal = model.get_predicted_span_signal(start_signal_logits, end_signal_logits, word_ids, rel_idx=rel_idx, max_window=5) |
|
|
|
|
|
if not has_signal: |
|
|
start_signal, end_signal = None, None |
|
|
|
|
|
start_signal_all.append(start_signal) |
|
|
end_signal_all.append(end_signal) |
|
|
|
|
|
|
|
|
|
|
|
def scores(rel_idx, start_cause, end_cause, start_effect, end_effect, start_signal, end_signal): |
|
|
|
|
|
start_cause_probs = torch.softmax(start_cause_logits[rel_idx], dim=-1) |
|
|
end_cause_probs = torch.softmax(end_cause_logits[rel_idx], dim=-1) |
|
|
start_effect_probs = torch.softmax(start_effect_logits[rel_idx], dim=-1) |
|
|
end_effect_probs = torch.softmax(end_effect_logits[rel_idx], dim=-1) |
|
|
|
|
|
start_signal_probs = torch.softmax(start_signal_logits[rel_idx], dim=-1) |
|
|
end_signal_probs = torch.softmax(end_signal_logits[rel_idx], dim=-1) |
|
|
|
|
|
|
|
|
sc = start_cause_probs[start_cause] |
|
|
ec = end_cause_probs[end_cause] |
|
|
se = start_effect_probs[start_effect] |
|
|
ee = end_effect_probs[end_effect] |
|
|
|
|
|
if has_signal: |
|
|
ss = start_signal_probs[start_signal] |
|
|
es = end_signal_probs[end_signal] |
|
|
total_score = round((sc * ec * se * ee * ss * es).item(), 4) |
|
|
ss_score = round(ss.item(), 4) |
|
|
es_score = round(es.item(), 4) |
|
|
else: |
|
|
ss_score = None |
|
|
es_score = None |
|
|
total_score = None |
|
|
|
|
|
total_score_nosignal = round((sc * ec * se * ee).item(), 4) |
|
|
|
|
|
|
|
|
log_sc = torch.log(sc + 1e-12) |
|
|
log_ec = torch.log(ec + 1e-12) |
|
|
log_se = torch.log(se + 1e-12) |
|
|
log_ee = torch.log(ee + 1e-12) |
|
|
sum_log_probs = log_sc + log_ec + log_se + log_ee |
|
|
|
|
|
return { |
|
|
'start_cause_score': round(sc.item(), 4), |
|
|
'end_cause_score': round(ec.item(), 4), |
|
|
'start_effect_score': round(se.item(), 4), |
|
|
'end_effect_score': round(ee.item(), 4), |
|
|
'start_signal_score': ss_score, |
|
|
'end_signal_score': es_score, |
|
|
'relation probability (sc * ec * se * ee * ss * es)': total_score, |
|
|
'relation probability (sc * ec * se * ee)': total_score_nosignal, |
|
|
'sum of log-probability scores (log_sc + log_ec + log_se + log_ee)': round(sum_log_probs.item(), 4), |
|
|
} |
|
|
|
|
|
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]) |
|
|
token_ids = inputs["input_ids"][0] |
|
|
offset_mapping = inputs["offset_mapping"][0].tolist() |
|
|
|
|
|
def mark_text_by_position(original_text, start_token, end_token, color): |
|
|
"""Marks text in the original string based on character positions.""" |
|
|
|
|
|
if start_token is not None and end_token is not None: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if start_token > end_token: |
|
|
return None |
|
|
|
|
|
if start_token <= end_token: |
|
|
|
|
|
|
|
|
start_idx, end_idx = offset_mapping[start_token][0], offset_mapping[end_token][1] |
|
|
|
|
|
if start_idx is not None and end_idx is not None and start_idx < end_idx: |
|
|
|
|
|
|
|
|
return ( |
|
|
original_text[:start_idx] |
|
|
+ f"<mark style='background-color:{color}; padding:2px; border-radius:4px;'>" |
|
|
+ original_text[start_idx:end_idx] |
|
|
+ "</mark>" |
|
|
+ original_text[end_idx:] |
|
|
) |
|
|
|
|
|
return original_text |
|
|
|
|
|
|
|
|
|
|
|
cause_text1 = mark_text_by_position(input_text, start_cause_all[0], end_cause_all[0], "#FFD700") |
|
|
effect_text1 = mark_text_by_position(input_text, start_effect_all[0], end_effect_all[0], "#90EE90") |
|
|
|
|
|
if has_signal: |
|
|
signal_text1 = mark_text_by_position(input_text, start_signal_all[0], end_signal_all[0], "#FF6347") |
|
|
else: |
|
|
signal_text1 = None |
|
|
|
|
|
cause_text2 = mark_text_by_position(input_text, start_cause_all[1], end_cause_all[1], "#FFD700") |
|
|
effect_text2 = mark_text_by_position(input_text, start_effect_all[1], end_effect_all[1], "#90EE90") |
|
|
|
|
|
if has_signal: |
|
|
signal_text2 = mark_text_by_position(input_text, start_signal_all[1], end_signal_all[1], "#FF6347") |
|
|
else: |
|
|
signal_text2 = None |
|
|
|
|
|
cause_text3 = mark_text_by_position(input_text, start_cause_all[2], end_cause_all[2], "#FFD700") |
|
|
effect_text3 = mark_text_by_position(input_text, start_effect_all[2], end_effect_all[2], "#90EE90") |
|
|
|
|
|
if has_signal: |
|
|
signal_text3 = mark_text_by_position(input_text, start_signal_all[2], end_signal_all[2], "#FF6347") |
|
|
else: |
|
|
signal_text3 = None |
|
|
|
|
|
scores_rel1 = scores(0, start_cause_all[0], end_cause_all[0], start_effect_all[0], end_effect_all[0], start_signal_all[0], end_signal_all[0]) |
|
|
scores_rel2 = scores(1, start_cause_all[1], end_cause_all[1], start_effect_all[1], end_effect_all[1], start_signal_all[0], end_signal_all[1]) |
|
|
scores_rel3 = scores(2, start_cause_all[2], end_cause_all[2], start_effect_all[2], end_effect_all[2], start_signal_all[2], end_signal_all[2]) |
|
|
|
|
|
return cause_text1, effect_text1, signal_text1, cause_text2, effect_text2, signal_text2, cause_text3, effect_text3, signal_text3, scores_rel1, scores_rel2, scores_rel3 |
|
|
|
|
|
|
|
|
st.title("Causal Relation Extraction") |
|
|
st.subheader("Multi-Relation Model - With joint signal classification & Without overlap penalization") |
|
|
input_text = st.text_area("Enter your text here:", height=100) |
|
|
|
|
|
|
|
|
if st.button("Extract"): |
|
|
if input_text: |
|
|
cause_text1, effect_text1, signal_text1, cause_text2, effect_text2, signal_text2, cause_text3, effect_text3, signal_text3, scores_rel1, scores_rel2, scores_rel3 = extract_arguments(input_text, tokenizer, model) |
|
|
|
|
|
|
|
|
|
|
|
log_scores = { |
|
|
"Relation_1": scores_rel1['sum of log-probability scores (log_sc + log_ec + log_se + log_ee)'], |
|
|
"Relation_2": scores_rel2['sum of log-probability scores (log_sc + log_ec + log_se + log_ee)'], |
|
|
"Relation_3": scores_rel3['sum of log-probability scores (log_sc + log_ec + log_se + log_ee)'], |
|
|
} |
|
|
|
|
|
|
|
|
sorted_log_scores = sorted(log_scores.items(), key=lambda x: x[1], reverse=True) |
|
|
|
|
|
st.write("RANKING - Sum of log-probability scores (log_sc + log_ec + log_se + log_ee):") |
|
|
for rel, score in sorted_log_scores: |
|
|
st.write(f"{rel}: {score}") |
|
|
|
|
|
|
|
|
|
|
|
st.write("## Relation 1:") |
|
|
|
|
|
if cause_text1 is None or effect_text1 is None: |
|
|
st.write("The prediction is not correct for at least one span.") |
|
|
else: |
|
|
st.markdown(f"**Cause:** {cause_text1}", unsafe_allow_html=True) |
|
|
st.markdown(f"**Effect:** {effect_text1}", unsafe_allow_html=True) |
|
|
st.markdown(f"**Signal:** {signal_text1}", unsafe_allow_html=True) |
|
|
st.markdown(f"<strong>Scores:</strong>", unsafe_allow_html=True) |
|
|
st.write(scores_rel1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.write("## Relation 2:") |
|
|
if cause_text2 is None or effect_text2 is None: |
|
|
st.write("The prediction is not correct for at least one span.") |
|
|
else: |
|
|
st.markdown(f"**Cause:** {cause_text2}", unsafe_allow_html=True) |
|
|
st.markdown(f"**Effect:** {effect_text2}", unsafe_allow_html=True) |
|
|
st.markdown(f"**Signal:** {signal_text2}", unsafe_allow_html=True) |
|
|
st.markdown(f"<strong>Scores:</strong>", unsafe_allow_html=True) |
|
|
st.write(scores_rel2) |
|
|
|
|
|
|
|
|
st.write("## Relation 3:") |
|
|
if cause_text3 is None or effect_text3 is None: |
|
|
st.write("The prediction is not correct for at least one span.") |
|
|
else: |
|
|
st.markdown(f"**Cause:** {cause_text3}", unsafe_allow_html=True) |
|
|
st.markdown(f"**Effect:** {effect_text3}", unsafe_allow_html=True) |
|
|
st.markdown(f"**Signal:** {signal_text3}", unsafe_allow_html=True) |
|
|
st.markdown(f"<strong>Scores:</strong>", unsafe_allow_html=True) |
|
|
st.write(scores_rel3) |
|
|
|
|
|
|
|
|
|
|
|
else: |
|
|
st.warning("Please enter some text before extracting.") |