rhasan/empathy
1
1import os2import time3from pathlib import Path4 5import gradio as gr6from huggingface_hub import hf_hub_download7 8from src.infer import load_model, predict9 10os.environ.setdefault("HF_HOME", str(Path.home() / ".cache" / "huggingface"))11 12# --------------------------------------------------------------------------- #13# Model loading (inference logic unchanged from the original app)14# --------------------------------------------------------------------------- #15_model_ready = False16 17EXAMPLES = [18 [19 "A month after Hurricane Matthew, 800,000 Haitians urgently need food.",20 "My heart just breaks for the people who are suffering.",21 ],22 [23 "A month after Hurricane Matthew, 800,000 Haitians urgently need food.",24 "I see, but this doesn't sound too worrisome to me.",25 ],26]27 28def _warmup():29 """Download + load the checkpoint once, then reuse it for every request."""30 global _model_ready31 if _model_ready:32 return33 t0 = time.time()34 ckpt_path = hf_hub_download(35 repo_id="rhasan/UPLME",36 filename="UPLME_NewsEmp_tuned-lambdas.ckpt",37 repo_type="model",38 )39 load_model(ckpt_path)40 _model_ready = True41 return f"Model loaded in {time.time() - t0:.1f} s."42 43 44# --------------------------------------------------------------------------- #45# Result rendering (theme-aware HTML; the 95% CI is the signature element)46# --------------------------------------------------------------------------- #47def render_result(mean: float, ci_low: float, ci_upp: float) -> str:48 mean_pct = min(100.0, max(0.0, mean))49 low_pct = min(100.0, max(0.0, ci_low))50 upp_pct = min(100.0, max(0.0, ci_upp))51 band_w = max(0.0, upp_pct - low_pct)52 return f"""53<div class="uplme-result">54 <div class="uplme-eyebrow">Empathy estimate</div>55 <div class="uplme-score">56 <span class="uplme-score-num">{mean:.1f}</span>57 <span class="uplme-score-den">/ 100</span>58 </div>59 <div class="uplme-ci">95% confidence interval · {ci_low:.1f} – {ci_upp:.1f}</div>60 <div class="uplme-meter">61 <div class="uplme-track">62 <div class="uplme-band" style="left:{low_pct:.2f}%; width:{band_w:.2f}%;"></div>63 <div class="uplme-marker" style="left:{mean_pct:.2f}%;"></div>64 </div>65 <div class="uplme-scale"><span>0</span><span>50</span><span>100</span></div>66 </div>67 <div class="uplme-legend"><span>Lower empathy</span><span>Higher empathy</span></div>68 <div class="uplme-note">The shaded band is the model's 95% confidence range; a wider band means greater uncertainty.</div>69</div>70""".strip()71 72 73PLACEHOLDER_HTML = """74<div class="uplme-result uplme-placeholder">75 <div class="uplme-placeholder-text">76 Enter a <b>stimulus</b> and a <b>response</b>, then press <b>Predict empathy</b>.77 You'll see an empathy score on a 0–100 scale together with the model's 95% confidence interval.78 </div>79</div>80""".strip()81 82 83WARNING_HTML = """84<div class="uplme-result uplme-warning">85 <div class="uplme-placeholder-text">86 Both fields are needed. Add a <b>stimulus</b> and a <b>response</b>, then press Predict empathy.87 </div>88</div>89""".strip()90 91 92HEADER_HTML = """93<div class="uplme-header">94 <h1>Empathy Prediction <span>with Uncertainty Estimation</span></h1>95 <p>Estimate how much empathy a written response conveys toward a stimulus, reported with a96 calibrated 95% confidence interval rather than a single bare number – powered by the UPLME model.</p>97 <div class="uplme-badges">98 <a href="https://arxiv.org/abs/2508.03520" target="_blank" rel="noopener">Paper ↗</a>99 <a href="https://github.com/hasan-rakibul/UPLME" target="_blank" rel="noopener">Code ↗</a>100 </div>101</div>102""".strip()103 104 105# --------------------------------------------------------------------------- #106# Prediction (with the empty-field guard)107# --------------------------------------------------------------------------- #108def predict_with_ci(article: str, essay: str) -> str:109 article = (article or "").strip()110 essay = (essay or "").strip()111 112 # Guard: do not run the model unless both fields are provided.113 if not article or not essay:114 gr.Warning("Please fill in both the stimulus and the response before predicting.")115 return WARNING_HTML116 117 _warmup()118 mean, var = predict(essay, article) # UPLME expects the (essay, article) order119 120 # Model outputs are in [1, 7]; rescale linearly to [0, 100].121 scale = 100 / 6122 mean = (float(mean) - 1) * scale123 std = (float(var) ** 0.5) * scale124 125 ci_low = max(0.0, mean - 1.96 * std)126 ci_upp = min(100.0, mean + 1.96 * std)127 return render_result(mean, ci_low, ci_upp)128 129 130def clear_all():131 return "", "", PLACEHOLDER_HTML132 133 134# --------------------------------------------------------------------------- #135# Theme + styling136# --------------------------------------------------------------------------- #137theme = gr.themes.Soft(138 primary_hue="blue",139 neutral_hue="slate",140 spacing_size="md",141 radius_size="lg",142 text_size="md",143 font=["system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica", "Arial", "sans-serif"],144)145 146CSS = """147.gradio-container { max-width: 1080px !important; margin: 0 auto !important; }148 149/* ---- Header ---- */150.uplme-header { text-align: center; padding: 10px 0 2px; }151.uplme-header h1 {152 font-size: 2rem; font-weight: 750; margin: 0 0 8px; line-height: 1.15; letter-spacing: -0.015em;153}154.uplme-header h1 span { font-weight: 420; opacity: 0.62; }155.uplme-header p {156 max-width: 660px; margin: 0 auto 14px; font-size: 1.02rem; line-height: 1.55; opacity: 0.78;157}158.uplme-badges { display: flex; gap: 10px; justify-content: center; }159.uplme-badges a {160 text-decoration: none; font-size: 0.85rem; font-weight: 600;161 padding: 5px 15px; border-radius: 999px;162 color: #2563eb; color: var(--color-accent, #2563eb);163 border: 1px solid #2563eb66;164 border-color: color-mix(in srgb, var(--color-accent, #2563eb) 38%, transparent);165 transition: background 0.15s ease;166}167.uplme-badges a:hover {168 background: #2563eb14;169 background: color-mix(in srgb, var(--color-accent, #2563eb) 12%, transparent);170}171 172/* ---- Predict button fills its cell ---- */173#predict-btn { width: 100%; }174 175/* ---- Result panel ---- */176.uplme-result { padding: 8px 6px; min-height: 180px; }177.uplme-eyebrow {178 text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.72rem;179 font-weight: 650; opacity: 0.55; margin-bottom: 6px;180}181.uplme-score { display: flex; align-items: baseline; gap: 8px; }182.uplme-score-num {183 font-size: 3.4rem; font-weight: 760; line-height: 1;184 color: #2563eb; color: var(--color-accent, #2563eb);185 font-variant-numeric: tabular-nums;186}187.uplme-score-den { font-size: 1.05rem; opacity: 0.5; font-weight: 500; }188.uplme-ci {189 margin-top: 8px; font-size: 0.95rem; opacity: 0.82;190 font-variant-numeric: tabular-nums;191}192 193.uplme-meter { margin-top: 28px; }194.uplme-track {195 position: relative; height: 14px; border-radius: 999px;196 background: rgba(128, 128, 128, 0.16);197}198.uplme-band {199 position: absolute; top: 0; bottom: 0; border-radius: 999px;200 background: #2563eb40;201 background: color-mix(in srgb, var(--color-accent, #2563eb) 26%, transparent);202}203.uplme-marker {204 position: absolute; top: -4px; bottom: -4px; width: 3px;205 transform: translateX(-50%); border-radius: 3px;206 background: #2563eb; background: var(--color-accent, #2563eb);207 box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent, #2563eb) 16%, transparent);208}209.uplme-scale {210 display: flex; justify-content: space-between; margin-top: 10px;211 font-size: 0.72rem; opacity: 0.5; font-variant-numeric: tabular-nums;212}213.uplme-legend {214 display: flex; justify-content: space-between; margin-top: 16px;215 font-size: 0.8rem; opacity: 0.66;216}217.uplme-note {218 margin-top: 16px; font-size: 0.78rem; line-height: 1.45;219 opacity: 0.55; font-style: italic;220}221 222/* ---- Empty / warning states ---- */223.uplme-placeholder, .uplme-warning {224 display: flex; align-items: center; min-height: 180px;225}226.uplme-placeholder-text { font-size: 0.98rem; line-height: 1.6; opacity: 0.62; }227.uplme-warning {228 padding-left: 16px;229 border-left: 3px solid #e0a300;230}231.uplme-warning .uplme-placeholder-text { opacity: 0.88; }232 233/* ---- One quiet entrance for results ---- */234@media (prefers-reduced-motion: no-preference) {235 .uplme-result { animation: uplme-in 0.35s ease both; }236 @keyframes uplme-in {237 from { opacity: 0; transform: translateY(6px); }238 to { opacity: 1; transform: translateY(0); }239 }240}241 242@media (max-width: 640px) {243 .uplme-header h1 { font-size: 1.6rem; }244 .uplme-score-num { font-size: 2.8rem; }245}246"""247 248 249# --------------------------------------------------------------------------- #250# UI251# --------------------------------------------------------------------------- #252with gr.Blocks(title="UPLME — Empathy Prediction") as demo:253 gr.HTML(HEADER_HTML)254 255 with gr.Row(equal_height=False):256 with gr.Column(scale=1):257 with gr.Group():258 article_input = gr.Textbox(259 label="Stimulus",260 info="The text the person is reacting to — e.g. a news article or situation.",261 value=EXAMPLES[0][0],262 placeholder="A month after Hurricane Matthew, 800,000 Haitians urgently need food.",263 lines=6,264 )265 essay_input = gr.Textbox(266 label="Response",267 info="The person's written reaction to the stimulus.",268 value=EXAMPLES[0][1],269 placeholder="My heart just breaks for the people who are suffering.",270 lines=6,271 )272 with gr.Row():273 clear_btn = gr.Button("Clear", variant="secondary", scale=1)274 predict_btn = gr.Button(275 "Predict empathy", variant="primary", scale=2, elem_id="predict-btn"276 )277 278 gr.Examples(279 label="Or try an example",280 examples=EXAMPLES,281 inputs=[article_input, essay_input],282 )283 284 with gr.Column(scale=1):285 result_html = gr.HTML(PLACEHOLDER_HTML)286 287 with gr.Accordion("About this model", open=False):288 gr.Markdown(289 """290**UPLME: Uncertainty-Aware Probabilistic Language Modelling for Robust Empathy Regression*291 292This demo predicts an empathy score on a 0–100 scale for a *response* (e.g. an essay) written toward a293*stimulus* (e.g. a news article), and reports a 95% confidence interval reflecting the model's uncertainty.294Unlike a single point prediction, the interval communicates *how confident* the model is — a core idea of295trustworthy, uncertainty-aware AI.296 297Authors: Md Rakibul Hasan, Md Zakir Hossain, Aneesh Krishna, Shafin Rahman and Tom Gedeon.298"""299 )300 301 predict_btn.click(predict_with_ci, inputs=[article_input, essay_input], outputs=result_html)302 clear_btn.click(clear_all, outputs=[article_input, essay_input, result_html])303 304 305if __name__ == "__main__":306 demo.queue().launch(ssr_mode=False, theme=theme, css=CSS)