# !pip install gradio -q # !pip install transformers -q # %% 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 # %% MODEL_NAMES = [ "bert-base-uncased", "roberta-base", "bert-large-uncased", "roberta-large", ] OWN_MODEL_NAME = "add-a-model" DECIMAL_PLACES = 1 EPS = 1e-5 # to avoid /0 errors # %% # Fire up the models models = dict() for bert_like in MODEL_NAMES: models[bert_like] = pipeline("fill-mask", model=bert_like) # %% def clean_tokens(tokens): return [token.strip() for token in tokens] def prepare_text_for_masking(input_text, mask_token, gendered_tokens, split_key): text_w_masks_list = [ mask_token if word.lower() in gendered_tokens else word for word in input_text.split() ] num_masks = len([m for m in text_w_masks_list if m == mask_token]) text_portions = " ".join(text_w_masks_list).split(split_key) return text_portions, num_masks def get_avg_prob_from_pipeline_outputs(mask_filled_text, gendered_token, num_preds): pronoun_preds = [ sum( [ pronoun["score"] if pronoun["token_str"].strip().lower() in gendered_token else 0.0 for pronoun in top_preds ] ) for top_preds in mask_filled_text ] return round(sum(pronoun_preds) / (EPS + num_preds) * 100, DECIMAL_PLACES) def get_figure(df, gender, n_fit=1): df = df.set_index("x-axis") cols = df.columns xs = list(range(len(df))) ys = df[cols[0]] fig, ax = plt.subplots() # Trying small fig due to rendering issues on HF, not on VS Code fig.set_figheight(3) fig.set_figwidth(9) # find stackoverflow reference p, C_p = np.polyfit(xs, ys, n_fit, cov=1) t = np.linspace(min(xs) - 1, max(xs) + 1, 10 * len(xs)) TT = np.vstack([t ** (n_fit - i) for i in range(n_fit + 1)]).T # matrix multiplication calculates the polynomial values yi = np.dot(TT, p) C_yi = np.dot(TT, np.dot(C_p, TT.T)) # C_y = TT*C_z*TT.T sig_yi = np.sqrt(np.diag(C_yi)) # Standard deviations are sqrt of diagonal ax.fill_between(t, yi + sig_yi, yi - sig_yi, alpha=0.25) ax.plot(t, yi, "-") ax.plot(df, "ro") ax.legend(list(df.columns)) ax.axis("tight") ax.set_xlabel("Value injected into input text") ax.set_title(f"Probability of predicting {gender} tokens.") ax.set_ylabel(f"Softmax prob") ax.tick_params(axis="x", labelrotation=5) ax.set_ylim(0, 100) return fig # %% def predict_masked_tokens( model_name, own_model_name, group_a_tokens, group_b_tokens, indie_vars, split_key, normalizing, n_fit, input_text, ): """Run inference on input_text for each model type, returning df and plots of percentage of gender pronouns predicted as female and male in each target text. """ if model_name not in MODEL_NAMES: 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(",") group_a_tokens = clean_tokens(group_a_tokens.split(",")) group_b_tokens = clean_tokens(group_b_tokens.split(",")) text_segments, num_preds = prepare_text_for_masking( input_text, mask_token, group_b_tokens + group_a_tokens, split_key ) male_pronoun_preds = [] female_pronoun_preds = [] for indie_var in indie_vars_list: target_text = f"{indie_var}".join(text_segments) mask_filled_text = model(target_text) # Quick hack as realized return type based on how many MASKs in text. if type(mask_filled_text[0]) is not list: mask_filled_text = [mask_filled_text] female_pronoun_preds.append( get_avg_prob_from_pipeline_outputs( mask_filled_text, group_a_tokens, num_preds ) ) male_pronoun_preds.append( get_avg_prob_from_pipeline_outputs( mask_filled_text, group_b_tokens, num_preds ) ) if normalizing: total_gendered_probs = np.add(female_pronoun_preds, male_pronoun_preds) female_pronoun_preds = np.around( np.divide(female_pronoun_preds, total_gendered_probs + EPS) * 100, decimals=DECIMAL_PLACES, ) male_pronoun_preds = np.around( np.divide(male_pronoun_preds, total_gendered_probs + EPS) * 100, decimals=DECIMAL_PLACES, ) results_df = pd.DataFrame({"x-axis": indie_vars_list}) results_df["group_a"] = female_pronoun_preds results_df["group_b"] = male_pronoun_preds female_fig = get_figure( results_df.drop("group_b", axis=1), "group_a", n_fit, ) male_fig = get_figure( results_df.drop("group_a", axis=1), "group_b", n_fit, ) display_text = f"{random.choice(indie_vars_list)}".join(text_segments) return ( display_text, female_fig, male_fig, results_df, ) truck_fn_example = [ MODEL_NAMES[2], "", ", ".join(["truck", "pickup"]), ", ".join(["car", "sedan"]), ", ".join(["city", "neighborhood", "farm"]), "PLACE", "True", 1, ] def truck_1_fn(): return truck_fn_example + ["He loaded up his truck and drove to the PLACE."] def truck_2_fn(): return truck_fn_example + [ "He loaded up the bed of his truck and drove to the PLACE." ] # # %% demo = gr.Blocks() with demo: gr.Markdown("# Spurious Correlation Evaluation for Pre-trained LLMs") gr.Markdown("## Instructions for this Demo") gr.Markdown( "1) Click on one of the examples below to pre-populate the input fields." ) gr.Markdown( "2) Check out the pre-populated fields as you scroll down to the ['Hit Submit...'] button!" ) gr.Markdown( "3) Repeat steps (1) and (2) with more pre-populated inputs or with your own values in the input fields!" ) gr.Markdown( """The pre-populated inputs below are for a demo example of a location-vs-vehicle-type spurious correlation. We can see this spurious correlation largely disappears in the well-specified example text.