llm_uncertainty / app.py
emilylearning's picture
fix url
a46f7eb
# %%
import gradio as gr
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import random
from matplotlib.ticker import MaxNLocator
from transformers import pipeline
from winogender_sentences import get_sentences
OWN_MODEL_NAME = 'add-a-model'
PICK_YOUR_OWN_LABEL = 'pick-your-own'
MODEL_NAME_DICT = {
"roberta-large": "RoBERTa-large",
"bert-large-uncased": "BERT-large",
"roberta-base": "RoBERTa-base",
"bert-base-uncased": "BERT-base",
OWN_MODEL_NAME: "Your model's"
}
MODEL_NAMES = list(MODEL_NAME_DICT.keys())
DECIMAL_PLACES = 1
EPS = 1e-5 # to avoid /0 errors
NUM_PTS_TO_AVERAGE = 2
# Example date conts
DATE_SPLIT_KEY = "DATE"
START_YEAR = 1901
STOP_YEAR = 2016
NUM_PTS = 30
DATES = np.linspace(START_YEAR, STOP_YEAR, NUM_PTS).astype(int).tolist()
DATES = [f'{d}' for d in DATES]
GENDERED_LIST = [
['he', 'she'],
['him', 'her'],
['his', 'hers'],
["himself", "herself"],
['male', 'female'],
# ['man', 'woman'] Explicitly added in winogender extended sentences
['men', 'women'],
["husband", "wife"],
['father', 'mother'],
['boyfriend', 'girlfriend'],
['brother', 'sister'],
["actor", "actress"],
]
# %%
# Fire up the models
models = {m: pipeline("fill-mask", model=m)
for m in MODEL_NAMES if m != OWN_MODEL_NAME}
# %%
# Get the winogender sentences
winogender_sentences = get_sentences()
occs = sorted(list({sentence_id.split('_')[0]
for sentence_id in winogender_sentences}))
# %%
def get_gendered_token_ids():
male_gendered_tokens = [list[0] for list in GENDERED_LIST]
female_gendered_tokens = [list[1] for list in GENDERED_LIST]
return male_gendered_tokens, female_gendered_tokens
def get_winogender_texts(occ):
return [winogender_sentences[id] for id in winogender_sentences.keys() if id.split('_')[0] == occ]
def display_input_texts(occ, alt_text):
if occ == PICK_YOUR_OWN_LABEL:
texts = alt_text.split('\n')
else:
texts = get_winogender_texts(occ)
display_texts = [
f"{i+1}) {text}" for (i, text) in enumerate(texts)]
return "\n".join(display_texts), texts
def get_avg_prob_from_pipeline_outputs(pipeline_preds, gendered_tokens, num_preds):
pronoun_preds = [sum([
pronoun["score"] if pronoun["token_str"].strip(
).lower() in gendered_tokens else 0.0
for pronoun in top_preds])
for top_preds in pipeline_preds
]
return round(sum(pronoun_preds) / (EPS + num_preds) * 100, DECIMAL_PLACES)
def is_top_pred_gendered(pipeline_preds, gendered_tokens):
return pipeline_preds[0][0]['token_str'].strip().lower() in gendered_tokens
# %%
def get_figure(df, model_name, occ):
xs = df[df.columns[0]]
ys = df[df.columns[1]]
fig, ax = plt.subplots()
ax.bar(xs, ys)
ax.axis('tight')
ax.set_xlabel("Sentence number")
ax.set_ylabel("Specification Metric")
ax.set_title(
f"Task Specification Metric on {MODEL_NAME_DICT[model_name]} for '{occ}' sentences")
return fig
# %%
def predict_gender_pronouns(
model_name,
own_model_name,
texts,
occ,
):
"""Run inference on input_text for selected model type, returning Task Specification metric results.
"""
# TODO: make these selectable by user
indie_vars = ', '.join(DATES)
num_ave = NUM_PTS_TO_AVERAGE
# For debugging
print('input_texts', texts)
if model_name is None or model_name == '':
model_name = MODEL_NAMES[0]
model = models[model_name]
elif model_name == OWN_MODEL_NAME:
model = pipeline("fill-mask", model=own_model_name)
else:
model = models[model_name]
mask_token = model.tokenizer.mask_token
indie_vars_list = indie_vars.split(',')
male_gendered_tokens, female_gendered_tokens = get_gendered_token_ids()
masked_texts = [text.replace('MASK', mask_token) for text in texts]
all_uncertainty_f = {}
not_top_gendered = set()
for i, text in enumerate(masked_texts):
female_pronoun_preds = []
male_pronoun_preds = []
top_pred_gendered = True # Assume true unless told otherwise
print(f"{i+1}) {text}")
for indie_var in indie_vars_list[:num_ave] + indie_vars_list[-num_ave:]:
target_text = f"In {indie_var}: {text}"
pipeline_preds = model(target_text)
# Quick hack as realized return type based on how many MASKs in text.
if type(pipeline_preds[0]) is not list:
pipeline_preds = [pipeline_preds]
# If top-pred not gendered, record as such
if not is_top_pred_gendered(pipeline_preds, female_gendered_tokens + male_gendered_tokens):
top_pred_gendered = False
num_preds = 1 # By design
female_pronoun_preds.append(get_avg_prob_from_pipeline_outputs(
pipeline_preds,
female_gendered_tokens,
num_preds
))
male_pronoun_preds.append(get_avg_prob_from_pipeline_outputs(
pipeline_preds,
male_gendered_tokens,
num_preds
))
# Normalizing by all gendered predictions
total_gendered_probs = np.add(
female_pronoun_preds, male_pronoun_preds)
norm_female_pronoun_preds = np.around(
np.divide(female_pronoun_preds, total_gendered_probs+EPS)*100,
decimals=DECIMAL_PLACES
)
sent_idx = f"{i+1}" if top_pred_gendered else f"{i+1}*"
all_uncertainty_f[sent_idx] = round(abs((sum(norm_female_pronoun_preds[-num_ave:]) - sum(norm_female_pronoun_preds[:num_ave]))
/ num_ave), DECIMAL_PLACES)
uncertain_df = pd.DataFrame.from_dict(
all_uncertainty_f, orient='index', columns=['Specification Metric'])
uncertain_df = uncertain_df.reset_index().rename(
columns={'index': 'Sentence number'})
return (
target_text,
uncertain_df,
get_figure(uncertain_df, model_name, occ),
)
demo = gr.Blocks()
with demo:
input_texts = gr.Variable([])
gr.Markdown("**Detect Task Specification at Inference-time.**")
gr.Markdown("""This method exploits the specification-induced spurious correlations demonstrated in this
[Spurious Correlations Hugging Face Space](https://huggingface.co/spaces/emilylearning/spurious_correlation_evaluation) to detect task specification at inference-time.
For this method, well-specified tasks should have a lower specification metric value, and unspecified tasks should have a higher specification metric value.
""")
gr.Markdown("""As an example, see the figure below with test sentences from the [Winogender schema](https://aclanthology.org/N18-2002/) for the occupation of `Doctor`.
With a close read, you can see that only sentence numbers (3) and (4) are well-specified for the gendered pronoun resolution task:
the masked pronoun is coreferent with the `man` or `woman`; the remainder are unspecfied: the masked pronoun is coreferent with a gender-unspecified person.
In this example we have 100\% accurate detection with the specification metric near zero for only sentence (3) and (4).
<p align="center">
<img src="file/spec_metric_result.png" alt="results" width="500"/>
</p>
""")
gr.Markdown("**To test this for yourself, follow the numbered steps below to test one of the pre-loaded options.** Once you get the hang of it, you can load a new model and/or provide your own input texts.")
gr.Markdown(f"""1) Pick a preloaded BERT-like model.
*Note: RoBERTa-large performance is best.*
2) Pick an Occupation type from the Winogender Schemas evaluation set.
*Or select '{PICK_YOUR_OWN_LABEL}' (it need not be about an occupation).*
3) Click the first button to load input texts.
*Read the sentences to determine which two are well-specified for gendered pronoun coreference resolution. The rest are gender-unspecified.*
4) Click the second button to get Task Specification Metric results.
""")
with gr.Row():
model_name = gr.Radio(
MODEL_NAMES,
type="value",
label="1) Pick a preloaded BERT-like model (note: RoBERTa-large performance is best).",
)
own_model_name = gr.Textbox(
label=f"...Or, if you selected an '{OWN_MODEL_NAME}' model, put any Hugging Face pipeline model name \
(that supports the `fill-mask` task (see list at https://huggingface.co/models?pipeline_tag=fill-mask).",
)
with gr.Row():
occ_box = gr.Radio(
occs+[PICK_YOUR_OWN_LABEL], label=f"2) Pick an Occupation type from the Winogender Schemas evaluation set, or select '{PICK_YOUR_OWN_LABEL}'\
(it need not be about an occupation).")
with gr.Row():
alt_input_texts = gr.Textbox(
lines=2,
label=f"...Or, if you selected '{PICK_YOUR_OWN_LABEL}' above, add your own texts new-line delimited sentences here. Be sure\
to include a single MASK-ed out pronoun. \
If unsure on the required format, click an occupation above instead, to see some example input texts for this round."
)
with gr.Row():
get_text_btn = gr.Button("3) Click to load input texts.")
get_text_btn.click(
fn=display_input_texts,
inputs=[occ_box, alt_input_texts],
outputs=[gr.Textbox(
label='Numbered sentences for evaluation. Number below corresponds to number in x-axis of plot.'), input_texts],
)
with gr.Row():
uncertain_btn = gr.Button("4) Click to get Task Specification Metric results!")
gr.Markdown(
"""We expect a lower specification metric value for well-specified tasks.
Note: If there is an * by a sentence number, then at least one top prediction for that sentence was non-gendered.""")
with gr.Row():
female_fig = gr.Plot(type="auto")
with gr.Row():
female_df = gr.Dataframe()
with gr.Row():
display_text = gr.Textbox(
type="text", label="Sample of text fed to model")
uncertain_btn.click(
fn=predict_gender_pronouns,
inputs=[model_name, own_model_name, input_texts, occ_box],
# inputs=date_example,
outputs=[display_text, female_df, female_fig]
)
demo.launch(debug=True)
# %%