Final final updates to app.py
Browse files
app.py
CHANGED
|
@@ -207,6 +207,20 @@ def read_input(file, text):
|
|
| 207 |
return open(path, encoding="utf-8", errors="replace").read()
|
| 208 |
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
@GPU
|
| 211 |
def analyse(file, text):
|
| 212 |
raw = read_input(file, text)
|
|
@@ -232,6 +246,9 @@ def analyse(file, text):
|
|
| 232 |
s3 = (f"FAILED: interviewer found ({', '.join(removed)}) but nothing was left after removing them, "
|
| 233 |
f"because the astronaut's name tag was not recognised.")
|
| 234 |
s3 += f" Words left: {len(narrative.split()):,}."
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
#step 4
|
| 237 |
segs = segment(narrative)
|
|
@@ -243,6 +260,11 @@ def analyse(file, text):
|
|
| 243 |
return s1, s2, s3, "<h4>4. Segmenting</h4>FAILED: no text left to segment.", "", None, empty
|
| 244 |
segs = segs[:300] #keeps a free cpu quick
|
| 245 |
wc = [len(s.split()) for s in segs]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
s4 = (f"<h4>4. Segmenting</h4><b>{len(segs)} segment{"s" if len(segs) != 1 else ""}</b>, {min(wc)} to {max(wc)} words each{note}<br><br>" +
|
| 247 |
"".join(f"<div class='seg'><b>#{i + 1}</b> ({wc[i]} words) {html.escape(s[:220])}{'...' if len(s) > 220 else ''}</div>"
|
| 248 |
for i, s in enumerate(segs[:3])) +
|
|
@@ -252,14 +274,17 @@ def analyse(file, text):
|
|
| 252 |
v = np.array([vader_flags(s) for s in segs])
|
| 253 |
t = tfidf_probs(segs)
|
| 254 |
b = bert_probs(segs)
|
|
|
|
| 255 |
rows = ""
|
| 256 |
for i, c in enumerate(CATEGORIES):
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
f"<
|
| 262 |
-
f"
|
|
|
|
|
|
|
| 263 |
|
| 264 |
order = np.argsort(-b.max(axis=1)) #strongest segments first for shap
|
| 265 |
choices = [f"#{k + 1} (BERT max {b[k].max():.2f}) {segs[k][:60]}..." for k in order]
|
|
@@ -294,9 +319,15 @@ CSS = """
|
|
| 294 |
.gradio-container {background: radial-gradient(ellipse at top, #141b33 0%, #05070f 70%) !important;}
|
| 295 |
* {box-shadow: none !important;}
|
| 296 |
.seg {border: 1px solid #ffffff44; border-radius: 12px; padding: 8px 12px; margin: 6px 0; color: #e6ebff;}
|
| 297 |
-
table {width: 100%; border-collapse: collapse; color: #e6ebff;}
|
| 298 |
-
th, td {border-bottom: 1px solid #ffffff33; padding: 6px 8px; text-align: left;}
|
| 299 |
.small {color: #9aa4c4; font-size: 13px;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
"""
|
| 301 |
|
| 302 |
with gr.Blocks(title="SPACEMIND") as demo:
|
|
|
|
| 207 |
return open(path, encoding="utf-8", errors="replace").read()
|
| 208 |
|
| 209 |
|
| 210 |
+
def bar(k, n, colour):
|
| 211 |
+
#count + a thin bar showing what share of the segments got flagged
|
| 212 |
+
return (f"<td><div class='cell'><b>{k}</b><span class='bar'>"
|
| 213 |
+
f"<span style='width:{k / n * 100:.0f}%;background:{colour}'></span></span></div></td>")
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def meter(p, thr):
|
| 217 |
+
#track from 0 to 1, fill = top bert score, white tick = threshold
|
| 218 |
+
colour = "#818cf8" if p >= thr else "#4b5370"
|
| 219 |
+
return (f"<div class='cell'><span class='meter'><span class='fill' style='width:{p * 100:.0f}%;background:{colour}'></span>"
|
| 220 |
+
f"<span class='tick' style='left:{thr * 100:.0f}%'></span></span>"
|
| 221 |
+
f"<span class='small'>{p:.2f} / {thr:.2f}</span></div>")
|
| 222 |
+
|
| 223 |
+
|
| 224 |
@GPU
|
| 225 |
def analyse(file, text):
|
| 226 |
raw = read_input(file, text)
|
|
|
|
| 246 |
s3 = (f"FAILED: interviewer found ({', '.join(removed)}) but nothing was left after removing them, "
|
| 247 |
f"because the astronaut's name tag was not recognised.")
|
| 248 |
s3 += f" Words left: {len(narrative.split()):,}."
|
| 249 |
+
if method == "no_tags":
|
| 250 |
+
#no cover page and no NAME: tags --> probably not an oral history, the models still run but read with care
|
| 251 |
+
s3 += " Note: this does not look like a NASA oral history transcript, so the scores below are less meaningful."
|
| 252 |
|
| 253 |
#step 4
|
| 254 |
segs = segment(narrative)
|
|
|
|
| 260 |
return s1, s2, s3, "<h4>4. Segmenting</h4>FAILED: no text left to segment.", "", None, empty
|
| 261 |
segs = segs[:300] #keeps a free cpu quick
|
| 262 |
wc = [len(s.split()) for s in segs]
|
| 263 |
+
long = sum(w > MAX_WORDS for w in wc)
|
| 264 |
+
if long:
|
| 265 |
+
#one "sentence" over 300 words (tables, references, no full stops) can't be split, bert only reads the first 512 tokens
|
| 266 |
+
note += (f"<br><span class='small'>{long} segment{'s are' if long != 1 else ' is'} over {MAX_WORDS} words because the text "
|
| 267 |
+
f"has very long runs with no full stop, BERT only reads the first 512 tokens of those.</span>")
|
| 268 |
s4 = (f"<h4>4. Segmenting</h4><b>{len(segs)} segment{"s" if len(segs) != 1 else ""}</b>, {min(wc)} to {max(wc)} words each{note}<br><br>" +
|
| 269 |
"".join(f"<div class='seg'><b>#{i + 1}</b> ({wc[i]} words) {html.escape(s[:220])}{'...' if len(s) > 220 else ''}</div>"
|
| 270 |
for i, s in enumerate(segs[:3])) +
|
|
|
|
| 274 |
v = np.array([vader_flags(s) for s in segs])
|
| 275 |
t = tfidf_probs(segs)
|
| 276 |
b = bert_probs(segs)
|
| 277 |
+
n = len(segs)
|
| 278 |
rows = ""
|
| 279 |
for i, c in enumerate(CATEGORIES):
|
| 280 |
+
counts = [int(v[:, i].sum()), int((t[:, i] >= 0.5).sum()), int((b[:, i] >= BERT_THRESHOLDS[c]).sum())]
|
| 281 |
+
cells = "".join(bar(k, n, col) for k, col in zip(counts, ["#7c86a8", "#38bdf8", "#818cf8"]))
|
| 282 |
+
rows += f"<tr><td>{NAMES[c]}</td>{cells}<td>{meter(b[:, i].max(), BERT_THRESHOLDS[c])}</td></tr>"
|
| 283 |
+
s5 = (f"<h4>5. Classification: all three models</h4>"
|
| 284 |
+
f"<table><tr><th>Category</th><th>VADER</th><th>TF-IDF</th><th>BERT + LoRA</th><th>BERT top score vs threshold</th></tr>{rows}</table>"
|
| 285 |
+
f"<p class='small'>Segments flagged out of {n}. White line = BERT threshold, the bar fills to the top segment score "
|
| 286 |
+
f"(indigo if it passes). BERT is my final model. A research prototype, it flags passages for a person to read "
|
| 287 |
+
f"and does not diagnose anyone.</p>")
|
| 288 |
|
| 289 |
order = np.argsort(-b.max(axis=1)) #strongest segments first for shap
|
| 290 |
choices = [f"#{k + 1} (BERT max {b[k].max():.2f}) {segs[k][:60]}..." for k in order]
|
|
|
|
| 319 |
.gradio-container {background: radial-gradient(ellipse at top, #141b33 0%, #05070f 70%) !important;}
|
| 320 |
* {box-shadow: none !important;}
|
| 321 |
.seg {border: 1px solid #ffffff44; border-radius: 12px; padding: 8px 12px; margin: 6px 0; color: #e6ebff;}
|
| 322 |
+
table {width: 100%; border-collapse: collapse; border: none !important; color: #e6ebff;}
|
| 323 |
+
th, td {border: none !important; border-bottom: 1px solid #ffffff33 !important; padding: 6px 8px; text-align: left;}
|
| 324 |
.small {color: #9aa4c4; font-size: 13px;}
|
| 325 |
+
.cell {display: flex; align-items: center; gap: 8px;}
|
| 326 |
+
.bar {flex: 1; min-width: 40px; height: 6px; background: #ffffff14; border-radius: 3px; overflow: hidden;}
|
| 327 |
+
.bar span {display: block; height: 100%;}
|
| 328 |
+
.meter {position: relative; flex: 1; min-width: 80px; height: 8px; background: #ffffff14; border-radius: 4px;}
|
| 329 |
+
.meter .fill {position: absolute; left: 0; top: 0; bottom: 0; border-radius: 4px;}
|
| 330 |
+
.meter .tick {position: absolute; top: -4px; bottom: -4px; width: 2px; background: #fff;}
|
| 331 |
"""
|
| 332 |
|
| 333 |
with gr.Blocks(title="SPACEMIND") as demo:
|