ishaan1402 commited on
Commit
da0dba0
Β·
1 Parent(s): 105647e
Files changed (1) hide show
  1. src/streamlit_app.py +144 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,146 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import torch
3
+ import numpy as np
4
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
5
+
6
+ # ── Config ────────────────────────────────────────────────────────────────────
7
+ HF_MODEL_ID = "ishaan1402/cbt-thought-pattern-classifier"
8
+ THRESHOLD = 0.15
9
+ MAX_LENGTH = 256
10
+
11
+ PATTERN_CLASSES = [
12
+ "Catastrophizing",
13
+ "Discounting the positive",
14
+ "Labeling and mislabeling",
15
+ "Mental filtering",
16
+ "Jumping to conclusions: mind reading",
17
+ "Jumping to conclusions: Fortune-telling",
18
+ "Overgeneralization",
19
+ "Personalization",
20
+ "Black-and-white or polarized thinking / All or nothing thinking",
21
+ "Should statements",
22
+ "None"
23
+ ]
24
+
25
+ PATTERN_DESCRIPTIONS = {
26
+ "Catastrophizing": "Giving greater weight to the worst possible outcome.",
27
+ "Discounting the positive": "Rejecting positive experiences by insisting they don't count.",
28
+ "Labeling and mislabeling": "Attributing actions to character rather than situation.",
29
+ "Mental filtering": "Dwelling only on the negative details of a situation.",
30
+ "Jumping to conclusions: mind reading": "Inferring negative thoughts from someone's behaviour.",
31
+ "Jumping to conclusions: Fortune-telling": "Predicting negative outcomes of events.",
32
+ "Overgeneralization": "Making faulty generalisations from insufficient evidence.",
33
+ "Personalization": "Assigning disproportionate personal blame to oneself.",
34
+ "Black-and-white or polarized thinking / All or nothing thinking":
35
+ "Viewing things as either all good or all bad with no middle ground.",
36
+ "Should statements": "Demanding particular behaviours regardless of realistic circumstances.",
37
+ "None": "No unhelpful thought pattern detected.",
38
+ }
39
+
40
+ # ── Model loading (cached so it only runs once per session) ───────────────────
41
+ @st.cache_resource
42
+ def load_model():
43
+ tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_ID)
44
+ model = AutoModelForSequenceClassification.from_pretrained(HF_MODEL_ID)
45
+ model.eval()
46
+ return tokenizer, model
47
+
48
+ # ── Inference ─────────────────────────────────────────────────────────────────
49
+ def classify(thought: str, persona: str, tokenizer, model) -> dict:
50
+ input_text = f"Persona: {persona} | Thought: {thought}" if persona.strip() \
51
+ else f"Persona: | Thought: {thought}"
52
+
53
+ inputs = tokenizer(
54
+ input_text,
55
+ return_tensors="pt",
56
+ max_length=MAX_LENGTH,
57
+ truncation=True,
58
+ padding="max_length"
59
+ )
60
+
61
+ with torch.no_grad():
62
+ logits = model(**inputs).logits
63
+
64
+ probs = torch.softmax(logits, dim=-1).squeeze().numpy()
65
+ top_idx = int(np.argmax(probs))
66
+ top_label = PATTERN_CLASSES[top_idx]
67
+ confidence = float(probs[top_idx])
68
+
69
+ is_unhelpful = (top_label != "None") and (confidence >= THRESHOLD)
70
+
71
+ return {
72
+ "is_unhelpful": is_unhelpful,
73
+ "predicted_pattern": top_label if is_unhelpful else "None",
74
+ "confidence": confidence,
75
+ "distribution": {PATTERN_CLASSES[i]: float(p) for i, p in enumerate(probs)},
76
+ }
77
+
78
+ # ── UI ────────────────────────────────────────────────────────────────────────
79
+ st.set_page_config(page_title="CBT Thought Classifier", page_icon="🧠", layout="centered")
80
+
81
+ st.title("🧠 CBT Thought Pattern Classifier")
82
+ st.caption(
83
+ "Enter a thought below. The model will identify which unhelpful cognitive pattern "
84
+ "it exhibits, if any, based on Cognitive Behavioral Therapy (CBT) research."
85
+ )
86
+
87
+ st.divider()
88
+
89
+ persona = st.text_input(
90
+ "Persona (optional)",
91
+ placeholder="e.g. I am a college student. I love playing guitar.",
92
+ help="Adding context about the person improves classification accuracy."
93
+ )
94
+
95
+ thought = st.text_area(
96
+ "Thought",
97
+ placeholder="e.g. I failed this test, I'm going to fail my entire degree.",
98
+ height=120
99
+ )
100
+
101
+ classify_btn = st.button("Classify", type="primary", use_container_width=True)
102
+
103
+ if classify_btn:
104
+ if not thought.strip():
105
+ st.warning("Please enter a thought to classify.")
106
+ else:
107
+ with st.spinner("Loading model...") if "tokenizer" not in st.session_state else st.spinner("Classifying..."):
108
+ tokenizer, model = load_model()
109
+ result = classify(thought, persona, tokenizer, model)
110
+
111
+ st.divider()
112
+
113
+ pattern = result["predicted_pattern"]
114
+ confidence = result["confidence"]
115
+ unhelpful = result["is_unhelpful"]
116
+
117
+ # ── Result banner ──
118
+ if unhelpful:
119
+ st.error(f"**Unhelpful pattern detected:** {pattern}")
120
+ else:
121
+ st.success("**No unhelpful pattern detected** β€” this thought looks okay.")
122
+
123
+ # ── Pattern description ──
124
+ if pattern in PATTERN_DESCRIPTIONS:
125
+ st.info(f"πŸ“– **{pattern}:** {PATTERN_DESCRIPTIONS[pattern]}")
126
+
127
+ # ── Confidence ──
128
+ st.metric("Model confidence", f"{confidence:.1%}")
129
+
130
+ # ── Full distribution ──
131
+ with st.expander("See full probability distribution"):
132
+ dist = result["distribution"]
133
+ sorted_dist = sorted(dist.items(), key=lambda x: x[1], reverse=True)
134
+ for label, prob in sorted_dist:
135
+ bar_pct = int(prob * 100)
136
+ # Highlight the predicted class
137
+ label_display = f"**{label}**" if label == pattern else label
138
+ col1, col2 = st.columns([3, 1])
139
+ with col1:
140
+ st.markdown(label_display)
141
+ st.progress(bar_pct)
142
+ with col2:
143
+ st.markdown(f"`{prob:.3f}`")
144
 
145
+ st.divider()
146
+ st.caption("Model trained on the [PATTERNREFRAME dataset](https://github.com/facebookresearch/ParlAI/tree/main/projects/reframe_thoughts) Β· Built with RoBERTa-large")