FINAL FINAL UPDATES TO app.py
Browse files
app.py
CHANGED
|
@@ -22,7 +22,7 @@ from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
|
| 22 |
from peft import PeftModel
|
| 23 |
import shap
|
| 24 |
|
| 25 |
-
#nltk bits used by vader, tf-idf cleaning and the sentence splitter
|
| 26 |
for pkg in ["stopwords", "vader_lexicon", "punkt", "punkt_tab"]:
|
| 27 |
nltk.download(pkg, quiet=True)
|
| 28 |
|
|
@@ -49,19 +49,19 @@ VADER_COMPOUND, VADER_NEG = -0.30, 0.02
|
|
| 49 |
NO_GATE = {"OUTSIDE_COMMS"} #outside comms fires on the keyword alone
|
| 50 |
sia = SentimentIntensityAnalyzer()
|
| 51 |
|
| 52 |
-
#2. tf-idf + logistic regression
|
| 53 |
vec = joblib.load(os.path.join(HERE, "tfidf_vectorizer.pkl"))
|
| 54 |
tf_models = joblib.load(os.path.join(HERE, "tfidf_logistic_models.pkl"))
|
| 55 |
STOP = set(stopwords.words("english"))
|
| 56 |
stemmer = SnowballStemmer("english")
|
| 57 |
|
| 58 |
-
#3. bert-base-uncased + my lora adapter
|
| 59 |
|
| 60 |
BERT_THRESHOLDS = {"WORKLOAD": 0.65, "TEAM": 0.90, "ADJUSTMENT": 0.85, "OUTSIDE_COMMS": 0.90}
|
| 61 |
tok = AutoTokenizer.from_pretrained(HERE)
|
| 62 |
base = AutoModelForSequenceClassification.from_pretrained(
|
| 63 |
"bert-base-uncased", num_labels=4, problem_type="multi_label_classification")
|
| 64 |
-
bert = PeftModel.from_pretrained(base, HERE, torch_device="cpu").eval()
|
| 65 |
|
| 66 |
|
| 67 |
def vader_flags(seg):
|
|
@@ -156,14 +156,14 @@ def diarise(text):
|
|
| 156 |
return "\n\n".join(kept), "fallback", [s for s in speakers if s != astro], speakers, len(kept), len(turns)
|
| 157 |
|
| 158 |
|
| 159 |
-
#segmentation: whole sentences packed up to 300 words
|
| 160 |
MIN_WORDS, MAX_WORDS = 20, 300
|
| 161 |
|
| 162 |
|
| 163 |
def segment(text):
|
| 164 |
blocks = [b.strip() for b in text.split("\n\n") if b.strip()]
|
| 165 |
paras = blocks[:1]
|
| 166 |
-
for b in blocks[1:]:
|
| 167 |
if paras[-1][-1] in ".!?\"\u201d" and b[0].isupper():
|
| 168 |
paras.append(b)
|
| 169 |
else:
|
|
@@ -176,7 +176,7 @@ def segment(text):
|
|
| 176 |
chunks.append(p)
|
| 177 |
continue
|
| 178 |
sents = []
|
| 179 |
-
for s in sent_tokenize(p):
|
| 180 |
if sents and s[:1].islower():
|
| 181 |
sents[-1] += " " + s
|
| 182 |
else:
|
|
@@ -226,7 +226,7 @@ def analyse(file, text):
|
|
| 226 |
elif method == "fallback":
|
| 227 |
s3 = f"PASSED (fallback): no 'Interviewed by' line, kept the longest speaker. Removed: {', '.join(removed)}."
|
| 228 |
else:
|
| 229 |
-
s3 = "NO SPEAKER TAGS FOUND so the whole text is treated as the astronaut's words."
|
| 230 |
if method != "no_tags" and not narrative.strip():
|
| 231 |
#known failure from my report: a curly apostrophe or accent in the astronaut's tag (O’CONNOR) isn't matched
|
| 232 |
s3 = (f"FAILED: interviewer found ({', '.join(removed)}) but nothing was left after removing them, "
|
|
@@ -234,7 +234,7 @@ def analyse(file, text):
|
|
| 234 |
s3 += f" Words left: {len(narrative.split()):,}."
|
| 235 |
if method == "no_tags":
|
| 236 |
#no cover page and no NAME: tags --> probably not an oral history, the models still run but read with care
|
| 237 |
-
s3 += " Note: this does not look like a NASA oral history transcript
|
| 238 |
|
| 239 |
#step 4
|
| 240 |
segs = segment(narrative)
|
|
@@ -321,7 +321,7 @@ def find_files(pairs):
|
|
| 321 |
# the page
|
| 322 |
|
| 323 |
CSS = """
|
| 324 |
-
.gradio-container {background: radial-gradient(ellipse at top, #
|
| 325 |
* {box-shadow: none !important;}
|
| 326 |
.seg {border: 1px solid #ffffff44; border-radius: 12px; padding: 8px 12px; margin: 6px 0; color: #e6ebff;}
|
| 327 |
table {width: 100%; border-collapse: collapse; color: #e6ebff;}
|
|
@@ -333,7 +333,7 @@ with gr.Blocks(title="SPACEMIND") as demo:
|
|
| 333 |
gr.Markdown("# SPACEMIND")
|
| 334 |
|
| 335 |
with gr.Row():
|
| 336 |
-
#
|
| 337 |
with gr.Column():
|
| 338 |
textInput = gr.Textbox(label="Paste a transcript or a passage", lines=10)
|
| 339 |
fileInput = gr.File(label="...or upload a .txt / .pdf transcript", file_types=[".txt", ".pdf"])
|
|
@@ -345,13 +345,13 @@ with gr.Blocks(title="SPACEMIND") as demo:
|
|
| 345 |
heldOut = find_files(TEST_SET)
|
| 346 |
if heldOut:
|
| 347 |
gr.Examples([[p] for p, _ in heldOut], inputs=fileInput, example_labels=[l for _, l in heldOut],
|
| 348 |
-
label="Load a transcript from my test set")
|
| 349 |
tests = find_files(PIPELINE_TESTS)
|
| 350 |
if tests:
|
| 351 |
gr.Examples([[p] for p, _ in tests], inputs=fileInput, example_labels=[l for _, l in tests],
|
| 352 |
-
label="Pipeline tests")
|
| 353 |
|
| 354 |
-
#
|
| 355 |
with gr.Column():
|
| 356 |
out1 = gr.Textbox(label="1. Input (word count)")
|
| 357 |
out2 = gr.Textbox(label="2. Cleaning")
|
|
@@ -372,9 +372,9 @@ with gr.Blocks(title="SPACEMIND") as demo:
|
|
| 372 |
shapButton.click(explain, [state, segPick, catPick], shapOut)
|
| 373 |
|
| 374 |
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
|
| 379 |
-
|
| 380 |
-
|
|
|
|
| 22 |
from peft import PeftModel
|
| 23 |
import shap
|
| 24 |
|
| 25 |
+
#nltk bits used by vader, tf-idf cleaning and the sentence splitter
|
| 26 |
for pkg in ["stopwords", "vader_lexicon", "punkt", "punkt_tab"]:
|
| 27 |
nltk.download(pkg, quiet=True)
|
| 28 |
|
|
|
|
| 49 |
NO_GATE = {"OUTSIDE_COMMS"} #outside comms fires on the keyword alone
|
| 50 |
sia = SentimentIntensityAnalyzer()
|
| 51 |
|
| 52 |
+
#2. tf-idf + logistic regression
|
| 53 |
vec = joblib.load(os.path.join(HERE, "tfidf_vectorizer.pkl"))
|
| 54 |
tf_models = joblib.load(os.path.join(HERE, "tfidf_logistic_models.pkl"))
|
| 55 |
STOP = set(stopwords.words("english"))
|
| 56 |
stemmer = SnowballStemmer("english")
|
| 57 |
|
| 58 |
+
#3. bert-base-uncased + my lora adapter
|
| 59 |
|
| 60 |
BERT_THRESHOLDS = {"WORKLOAD": 0.65, "TEAM": 0.90, "ADJUSTMENT": 0.85, "OUTSIDE_COMMS": 0.90}
|
| 61 |
tok = AutoTokenizer.from_pretrained(HERE)
|
| 62 |
base = AutoModelForSequenceClassification.from_pretrained(
|
| 63 |
"bert-base-uncased", num_labels=4, problem_type="multi_label_classification")
|
| 64 |
+
bert = PeftModel.from_pretrained(base, HERE, torch_device="cpu").eval() #cpu --> zerogpu pretends cuda exists at startup, eval() turns dropout off
|
| 65 |
|
| 66 |
|
| 67 |
def vader_flags(seg):
|
|
|
|
| 156 |
return "\n\n".join(kept), "fallback", [s for s in speakers if s != astro], speakers, len(kept), len(turns)
|
| 157 |
|
| 158 |
|
| 159 |
+
#segmentation: whole sentences packed up to 300 words, nothing under 20 words
|
| 160 |
MIN_WORDS, MAX_WORDS = 20, 300
|
| 161 |
|
| 162 |
|
| 163 |
def segment(text):
|
| 164 |
blocks = [b.strip() for b in text.split("\n\n") if b.strip()]
|
| 165 |
paras = blocks[:1]
|
| 166 |
+
for b in blocks[1:]: #a blank line is only a real break if the sentence ended and the next starts with a capital
|
| 167 |
if paras[-1][-1] in ".!?\"\u201d" and b[0].isupper():
|
| 168 |
paras.append(b)
|
| 169 |
else:
|
|
|
|
| 176 |
chunks.append(p)
|
| 177 |
continue
|
| 178 |
sents = []
|
| 179 |
+
for s in sent_tokenize(p): #glue back "sentences" that start lowercase (punkt splits on initials)
|
| 180 |
if sents and s[:1].islower():
|
| 181 |
sents[-1] += " " + s
|
| 182 |
else:
|
|
|
|
| 226 |
elif method == "fallback":
|
| 227 |
s3 = f"PASSED (fallback): no 'Interviewed by' line, kept the longest speaker. Removed: {', '.join(removed)}."
|
| 228 |
else:
|
| 229 |
+
s3 = "NO SPEAKER TAGS FOUND (like ARMSTRONG:), so the whole text is treated as the astronaut's words."
|
| 230 |
if method != "no_tags" and not narrative.strip():
|
| 231 |
#known failure from my report: a curly apostrophe or accent in the astronaut's tag (O’CONNOR) isn't matched
|
| 232 |
s3 = (f"FAILED: interviewer found ({', '.join(removed)}) but nothing was left after removing them, "
|
|
|
|
| 234 |
s3 += f" Words left: {len(narrative.split()):,}."
|
| 235 |
if method == "no_tags":
|
| 236 |
#no cover page and no NAME: tags --> probably not an oral history, the models still run but read with care
|
| 237 |
+
s3 += " Note: this does not look like a NASA oral history transcript, so the scores below are less meaningful."
|
| 238 |
|
| 239 |
#step 4
|
| 240 |
segs = segment(narrative)
|
|
|
|
| 321 |
# the page
|
| 322 |
|
| 323 |
CSS = """
|
| 324 |
+
.gradio-container {background: radial-gradient(ellipse at top, #2a0f1a 0%, #05070f 70%) !important;}
|
| 325 |
* {box-shadow: none !important;}
|
| 326 |
.seg {border: 1px solid #ffffff44; border-radius: 12px; padding: 8px 12px; margin: 6px 0; color: #e6ebff;}
|
| 327 |
table {width: 100%; border-collapse: collapse; color: #e6ebff;}
|
|
|
|
| 333 |
gr.Markdown("# SPACEMIND")
|
| 334 |
|
| 335 |
with gr.Row():
|
| 336 |
+
#inputs
|
| 337 |
with gr.Column():
|
| 338 |
textInput = gr.Textbox(label="Paste a transcript or a passage", lines=10)
|
| 339 |
fileInput = gr.File(label="...or upload a .txt / .pdf transcript", file_types=[".txt", ".pdf"])
|
|
|
|
| 345 |
heldOut = find_files(TEST_SET)
|
| 346 |
if heldOut:
|
| 347 |
gr.Examples([[p] for p, _ in heldOut], inputs=fileInput, example_labels=[l for _, l in heldOut],
|
| 348 |
+
label="Load a transcript from my held-out test set")
|
| 349 |
tests = find_files(PIPELINE_TESTS)
|
| 350 |
if tests:
|
| 351 |
gr.Examples([[p] for p, _ in tests], inputs=fileInput, example_labels=[l for _, l in tests],
|
| 352 |
+
label="Pipeline tests (where it works and where it breaks)")
|
| 353 |
|
| 354 |
+
#the stages
|
| 355 |
with gr.Column():
|
| 356 |
out1 = gr.Textbox(label="1. Input (word count)")
|
| 357 |
out2 = gr.Textbox(label="2. Cleaning")
|
|
|
|
| 372 |
shapButton.click(explain, [state, segPick, catPick], shapOut)
|
| 373 |
|
| 374 |
|
| 375 |
+
BURGUNDY = gr.themes.Color(c50="#fdf2f4", c100="#fbe5e9", c200="#f5c6d0", c300="#ec9aab", c400="#d9637c",
|
| 376 |
+
c500="#9b2743", c600="#801f38", c700="#661a2e", c800="#4f1524", c900="#3d1019",
|
| 377 |
+
c950="#2e0a14", name="burgundy")
|
| 378 |
|
| 379 |
+
demo.launch(theme=gr.themes.Base(primary_hue=BURGUNDY, neutral_hue="slate"), css=CSS,
|
| 380 |
+
js="() => document.body.classList.add('dark')")
|