gtech13 commited on
Commit
094ddd8
·
verified ·
1 Parent(s): 62ca490

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -0
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["OMP_NUM_THREADS"] = "1"
3
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
4
+
5
+ import streamlit as st
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ from sentence_transformers import SentenceTransformer
10
+ from sklearn.feature_extraction.text import CountVectorizer
11
+ import pandas as pd
12
+ import numpy as np
13
+ import time
14
+
15
+
16
+ # ==========================================
17
+ # 1. PAGE CONFIG & UI SETUP
18
+ # ==========================================
19
+ st.set_page_config(page_title="Neural VAE Architecture", layout="wide")
20
+
21
+ # (Make sure these two lines only appear ONCE in your entire file!)
22
+ st.title("🧠 Variational Autoencoder (VAE): Inside the Black Box")
23
+ st.markdown("Watch the mathematics of a Neural Network transform text into topics in real-time.")
24
+
25
+ # --- DISPLAY ARCHITECTURE DIAGRAM ---
26
+ try:
27
+ st.image("pipeline.png", use_container_width=True)
28
+ except Exception:
29
+ st.info("💡 Tip: Upload a 'pipeline.png' image to display the architecture diagram here.")
30
+
31
+ st.divider()
32
+
33
+ # --- CACHE THE HEAVY BERT MODEL ---
34
+ @st.cache_resource
35
+ def load_bert():
36
+ return SentenceTransformer("all-MiniLM-L6-v2")
37
+
38
+
39
+ # --- SIDEBAR CONTROLS ---
40
+ st.sidebar.header("1. Input & Text Prep")
41
+ n_topics = st.sidebar.slider("Number of Topics (Latent Dimensions)", 2, 10, 3)
42
+ words_per_theme = st.sidebar.slider("Top Words per Topic", 3, 10, 5)
43
+
44
+ st.sidebar.header("2. Neural Network Settings")
45
+ activation_choice = st.sidebar.selectbox("Encoder Activation", ["ReLU", "Softplus", "GELU"])
46
+ dropout_rate = st.sidebar.slider("Dropout Rate (Noise)", 0.0, 0.5, 0.2)
47
+
48
+ st.sidebar.header("3. Optimization (Loss)")
49
+ kl_beta = st.sidebar.slider("Max KL Beta (β)", 0.0, 1.0, 0.1)
50
+ kl_warmup = st.sidebar.slider("KL Annealing (Epochs)", 0, 500, 200)
51
+ learning_rate = st.sidebar.selectbox("Learning Rate", [0.001, 0.005, 0.01, 0.05], index=2)
52
+ epochs = st.sidebar.slider("Training Epochs", 100, 1000, 500)
53
+
54
+ sample_abstract = """
55
+ Students who registered for the Mapping with Google massive open online course (MOOC)
56
+ were asked several questions during the registration process to identify prior
57
+ experience with eleven skills as well as their goals for registering for the course.
58
+ At the end of the course, we compared students' self reports of goal achievement
59
+ with behavioral click-stream analysis. In addition, we assessed how well prior
60
+ skill in a subject predicts a student's course completion and found no correlation.
61
+ """
62
+ raw_data = st.sidebar.text_area("Training Data:", value=sample_abstract, height=150)
63
+
64
+ # ==========================================
65
+ # 2. THE PYTORCH VAE ARCHITECTURE
66
+ # ==========================================
67
+ class ContextualVAE(nn.Module):
68
+ def __init__(self, input_dim, hidden_dim, latent_dim, vocab_size, activation, dropout):
69
+ super().__init__()
70
+ act_fn = nn.ReLU() if activation == "ReLU" else nn.Softplus() if activation == "Softplus" else nn.GELU()
71
+
72
+ # 1. ENCODER
73
+ self.encoder = nn.Sequential(
74
+ nn.Linear(input_dim, hidden_dim),
75
+ act_fn,
76
+ nn.Dropout(dropout),
77
+ nn.BatchNorm1d(hidden_dim)
78
+ )
79
+ self.fc_mu = nn.Linear(hidden_dim, latent_dim)
80
+ self.fc_logvar = nn.Linear(hidden_dim, latent_dim)
81
+
82
+ # 2. DECODER
83
+ self.decoder = nn.Linear(latent_dim, vocab_size, bias=False)
84
+
85
+ def encode(self, x):
86
+ h = self.encoder(x)
87
+ return self.fc_mu(h), self.fc_logvar(h)
88
+
89
+ def reparameterize(self, mu, logvar):
90
+ std = torch.exp(0.5 * logvar)
91
+ eps = torch.randn_like(std)
92
+ return mu + eps * std
93
+
94
+ def forward(self, x):
95
+ mu, logvar = self.encode(x)
96
+ z = self.reparameterize(mu, logvar)
97
+ p_topic = F.softmax(z, dim=-1)
98
+ recon_log_probs = F.log_softmax(self.decoder(p_topic), dim=-1)
99
+ return recon_log_probs, mu, logvar, z
100
+
101
+ # ==========================================
102
+ # 3. THE ARCHITECTURE DASHBOARD (UI LAYOUT)
103
+ # ==========================================
104
+ st.divider()
105
+
106
+ col1, col2, col3 = st.columns(3)
107
+
108
+ with col1:
109
+ st.markdown("### 📥 1. The Encoder")
110
+ st.latex(r"\mu, \log(\sigma^2) = \text{NeuralNet}(X_{bert})")
111
+ st.caption("Compresses 384D BERT vectors into Mean and Variance coordinates.")
112
+ ui_mu = st.empty()
113
+ ui_var = st.empty()
114
+ with st.expander("View Encoder History"):
115
+ ui_encoder_table = st.empty()
116
+
117
+ with col2:
118
+ st.markdown("### 🕳️ 2. The Latent Space")
119
+ st.latex(r"Z = \mu + \sigma \odot \epsilon")
120
+ st.caption("The Reparameterization Trick. Samples a topic vector adding random noise.")
121
+ ui_z = st.empty()
122
+ ui_softmax = st.empty()
123
+ with st.expander("View Latent History"):
124
+ ui_latent_table = st.empty()
125
+
126
+ with col3:
127
+ st.markdown("### 📤 3. The Decoder")
128
+ st.latex(r"\hat{Y} = \text{Softmax}(Z) \cdot W_{decoder}")
129
+ st.caption("Maps the tiny topic vector back to the full Vocabulary size.")
130
+ ui_decoder = st.empty()
131
+ with st.expander("View Decoder History"):
132
+ ui_decoder_table = st.empty()
133
+
134
+ st.divider()
135
+ st.markdown("### 📉 4. Optimization (Backpropagation)")
136
+ st.latex(r"\mathcal{L} = D_{KL}(Y || \hat{Y}) + \beta \cdot D_{KL}(\mathcal{N}(\mu, \sigma) || \mathcal{N}(0, I))")
137
+
138
+ col_loss_text, col_loss_graph = st.columns([1, 2])
139
+ with col_loss_text:
140
+ ui_beta = st.empty()
141
+ ui_recon_loss = st.empty()
142
+ ui_kl_loss = st.empty()
143
+ with col_loss_graph:
144
+ loss_chart_placeholder = st.empty()
145
+
146
+ # ==========================================
147
+ # 4. TRAINING EXECUTION
148
+ # ==========================================
149
+ if st.sidebar.button("🚀 Start Backpropagation", type="primary", use_container_width=True):
150
+
151
+ sentences = [s.strip() for s in raw_data.split('.') if len(s.strip()) > 10]
152
+
153
+ # Data Prep
154
+ stop_w = list(CountVectorizer(stop_words='english').get_stop_words()) + ['students', 'course']
155
+ vectorizer = CountVectorizer(stop_words=stop_w)
156
+ bow_matrix = vectorizer.fit_transform(sentences).toarray()
157
+ vocab = vectorizer.get_feature_names_out()
158
+
159
+ target_dist = torch.tensor(bow_matrix).float()
160
+ target_dist = target_dist / (target_dist.sum(dim=1, keepdim=True) + 1e-10)
161
+
162
+ with st.spinner("Loading BERT (Cached)..."):
163
+ embedder = load_bert()
164
+ bert_inputs = torch.tensor(embedder.encode(sentences))
165
+
166
+ model = ContextualVAE(384, 128, n_topics, len(vocab), activation_choice, dropout_rate)
167
+ optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
168
+
169
+ recon_hist, kl_hist = [], []
170
+
171
+ # Trackers for the expanding history tables
172
+ history_encoder = []
173
+ history_latent = []
174
+ history_decoder = []
175
+
176
+ # --- PYTORCH TRAINING LOOP ---
177
+ for epoch in range(1, epochs + 1):
178
+ model.train()
179
+ optimizer.zero_grad()
180
+
181
+ recon, mu, logvar, z = model(bert_inputs)
182
+
183
+ recon_loss = F.kl_div(recon, target_dist, reduction='batchmean')
184
+ kl_loss = -0.5 * torch.mean(torch.sum(1 + logvar - mu.pow(2) - logvar.exp(), dim=1))
185
+
186
+ current_beta = kl_beta * min(1.0, epoch / max(1, kl_warmup))
187
+ total_loss = recon_loss + (current_beta * kl_loss)
188
+
189
+ total_loss.backward()
190
+ optimizer.step()
191
+
192
+ # ==========================================
193
+ # 1. RECORD DATA EVERY SINGLE EPOCH
194
+ # ==========================================
195
+ # Grab math for Sentence 1
196
+ s1_mu = np.round(mu[0].detach().numpy(), 3).tolist()
197
+ s1_var = np.round(torch.exp(0.5 * logvar[0]).detach().numpy(), 3).tolist()
198
+ s1_z = np.round(z[0].detach().numpy(), 3).tolist()
199
+ s1_soft = np.round(F.softmax(z[0], dim=-1).detach().numpy(), 3).tolist()
200
+
201
+ # Save to Encoder & Latent History
202
+ history_encoder.append({"Epoch": epoch, "Mean (μ)": str(s1_mu), "Variance (σ)": str(s1_var)})
203
+ history_latent.append({"Epoch": epoch, "Sampled Vector (Z)": str(s1_z), "Topic Probs": str(s1_soft)})
204
+
205
+ # Dynamically create separate columns for EACH topic in the Decoder history!
206
+ weights = model.decoder.weight.T
207
+ dec_dict = {"Epoch": epoch}
208
+ dec_text = ""
209
+
210
+ for i in range(n_topics):
211
+ top_idx = torch.argsort(weights[i], descending=True)[:words_per_theme]
212
+ theme_words = [vocab[j] for j in top_idx]
213
+
214
+ # Format for the UI Box
215
+ dec_text += f"**T{i+1}:** {', '.join(theme_words)}\n\n"
216
+ # Format for the DataFrame (Separate column for Topic 1, Topic 2, etc.)
217
+ dec_dict[f"Topic {i+1}"] = ", ".join(theme_words)
218
+
219
+ history_decoder.append(dec_dict)
220
+
221
+ recon_hist.append(recon_loss.item())
222
+ kl_hist.append((current_beta * kl_loss).item())
223
+
224
+ # ==========================================
225
+ # 2. SAFE UI RENDER (Draws the accumulated data every 20 epochs)
226
+ # ==========================================
227
+ if epoch % 20 == 0 or epoch == epochs:
228
+
229
+ # Update Live Values
230
+ ui_mu.info(f"**Mean (μ) for Sentence 1:**\n`{s1_mu}`")
231
+ ui_var.warning(f"**Variance (σ) for Sentence 1:**\n`{s1_var}`")
232
+ ui_z.success(f"**Sampled 'Z' for Sentence 1:**\n`{s1_z}`")
233
+ ui_softmax.error(f"**Topic Probabilities:**\n`{s1_soft}`")
234
+ ui_decoder.info(dec_text)
235
+
236
+ # Update Tables (These now contain ALL epochs up to this point!)
237
+ ui_encoder_table.dataframe(pd.DataFrame(history_encoder), hide_index=True, use_container_width=True)
238
+ ui_latent_table.dataframe(pd.DataFrame(history_latent), hide_index=True, use_container_width=True)
239
+ ui_decoder_table.dataframe(pd.DataFrame(history_decoder), hide_index=True, use_container_width=True)
240
+
241
+ # Update Optimization Metrics
242
+ ui_beta.metric("Current Beta (Annealing)", f"{current_beta:.4f}")
243
+ ui_recon_loss.metric("Reconstruction Loss", f"{recon_loss.item():.4f}")
244
+ ui_kl_loss.metric("KL Penalty", f"{(current_beta * kl_loss).item():.4f}")
245
+
246
+ # Update Graph
247
+ chart_df = pd.DataFrame({
248
+ "Recon Loss": recon_hist,
249
+ "KL Penalty": kl_hist
250
+ })
251
+ loss_chart_placeholder.line_chart(chart_df)
252
+
253
+ time.sleep(0.05)
254
+
255
+ st.success("Neural Network Training Complete!")