db-d2 commited on
Commit
8961a34
·
1 Parent(s): 5589f5e

Feat: initial demo publish

Browse files
Files changed (2) hide show
  1. app.py +167 -0
  2. requirements.txt +4 -0
app.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.optim as optim
6
+ import plotly.graph_objects as go
7
+
8
+ torch.manual_seed(42)
9
+ np.random.seed(42)
10
+
11
+ # Intercepts encode baseline preferences when no rivals are encountered.
12
+ # Luke pushes asparagus & milk; Rey pushes steak & milk.
13
+ B_true = np.array([
14
+ # intercepts: asp milk appl steak
15
+ [ 0.6, 0.3, 0.0, -0.2],
16
+ # Luke effect:
17
+ [ 0.8, 0.5, -0.2, -0.6],
18
+ # Rey effect:
19
+ [-0.6, 0.5, -0.3, 0.9],
20
+ ])
21
+
22
+ FOODS = ['Asparagus', 'Milk', 'Apples', 'Steak']
23
+ COLORS = ['#00CC00', '#0066FF', '#FF0000', '#FF9900']
24
+
25
+ # ============================================================================
26
+ # DATA & MODEL
27
+ # ============================================================================
28
+ def softmax(z):
29
+ z = np.atleast_2d(z)
30
+ z = z - np.max(z, axis=1, keepdims=True)
31
+ return np.exp(z) / np.sum(np.exp(z), axis=1, keepdims=True)
32
+
33
+ def generate_logs(n=100):
34
+ """Simulate Kylo's ~100 weeks of logs."""
35
+ luke = np.random.randint(0, 6, n)
36
+ rey = np.random.randint(0, 6, n)
37
+ X = np.column_stack([np.ones(n), luke, rey])
38
+ probs = softmax(X @ B_true)
39
+ choices = np.array([np.random.choice(4, p=p) for p in probs])
40
+ return X, choices
41
+
42
+ class SoftmaxModel(nn.Module):
43
+ def __init__(self):
44
+ super().__init__()
45
+ self.linear = nn.Linear(3, 4, bias=False)
46
+
47
+ def forward(self, x):
48
+ return self.linear(x)
49
+
50
+ def train_model(X, y):
51
+ """Fit multinomial logistic regression in PyTorch."""
52
+ model = SoftmaxModel()
53
+ opt = optim.Adam(model.parameters(), lr=0.5)
54
+ loss_fn = nn.CrossEntropyLoss()
55
+ X_t = torch.tensor(X, dtype=torch.float32)
56
+ y_t = torch.tensor(y, dtype=torch.long)
57
+
58
+ for _ in range(500):
59
+ opt.zero_grad()
60
+ loss_fn(model(X_t), y_t).backward()
61
+ opt.step()
62
+
63
+ return model.linear.weight.detach().numpy().T
64
+
65
+ # Train model once on startup
66
+ X_train, y_train = generate_logs(100)
67
+ B_learned = train_model(X_train, y_train)
68
+
69
+ # ============================================================================
70
+ # SIMPLEX VISUALIZATION
71
+ # ============================================================================
72
+ def make_simplex(luke, rey):
73
+ """Show predicted probabilities on tetrahedron for given encounters."""
74
+ # Predict using learned model
75
+ x = np.array([[1, luke, rey]])
76
+ probs = softmax(x @ B_learned)[0]
77
+
78
+ # Tetrahedron vertices
79
+ v = np.array([
80
+ [1, 0, -1/np.sqrt(2)],
81
+ [-1, 0, -1/np.sqrt(2)],
82
+ [0, 1, 1/np.sqrt(2)],
83
+ [0, -1, 1/np.sqrt(2)],
84
+ ])
85
+
86
+ # Predicted point location
87
+ pt = probs @ v
88
+
89
+ fig = go.Figure()
90
+
91
+ # Draw tetrahedron edges
92
+ for i, j in [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)]:
93
+ fig.add_trace(go.Scatter3d(
94
+ x=[v[i,0], v[j,0]], y=[v[i,1], v[j,1]], z=[v[i,2], v[j,2]],
95
+ mode='lines', line=dict(color='gray', width=2),
96
+ showlegend=False, hoverinfo='skip'
97
+ ))
98
+
99
+ # Draw predicted point
100
+ fig.add_trace(go.Scatter3d(
101
+ x=[pt[0]], y=[pt[1]], z=[pt[2]],
102
+ mode='markers', marker=dict(size=12, color='white', symbol='diamond'),
103
+ name='Prediction'
104
+ ))
105
+
106
+ # Vertex labels with probabilities
107
+ for i, (food, color) in enumerate(zip(FOODS, COLORS)):
108
+ label = f"{food}\n({probs[i]:.1%})"
109
+ fig.add_trace(go.Scatter3d(
110
+ x=[v[i,0]*1.3], y=[v[i,1]*1.3], z=[v[i,2]*1.3],
111
+ mode='text', text=[label], textfont=dict(size=14, color=color),
112
+ showlegend=False, hoverinfo='skip'
113
+ ))
114
+
115
+ fig.update_layout(
116
+ title=f"Luke: {luke}, Rey: {rey}",
117
+ scene=dict(
118
+ xaxis=dict(visible=False),
119
+ yaxis=dict(visible=False),
120
+ zaxis=dict(visible=False),
121
+ bgcolor='#1a1a1a'
122
+ ),
123
+ paper_bgcolor='#1a1a1a',
124
+ font=dict(color='white'),
125
+ margin=dict(l=0, r=0, t=40, b=0),
126
+ height=500
127
+ )
128
+ return fig
129
+
130
+ def predict(luke, rey):
131
+ x = np.array([[1, luke, rey]])
132
+ probs = softmax(x @ B_learned)[0]
133
+ fig = make_simplex(int(luke), int(rey))
134
+
135
+ text = f"**Predicted probabilities for {int(luke)} Luke, {int(rey)} Rey encounters:**\n\n"
136
+ for food, p, color in zip(FOODS, probs, COLORS):
137
+ text += f"- {food}: {p:.1%}\n"
138
+
139
+ return fig, text
140
+
141
+ # ============================================================================
142
+ # GRADIO APP
143
+ # ============================================================================
144
+ with gr.Blocks(title="Kylo's Cravings") as demo:
145
+ gr.Markdown("# Kylo Ren's Craving Predictor")
146
+ gr.Markdown("Model trained on ~100 weeks of logs. Adjust sliders to predict cravings.")
147
+
148
+ with gr.Row():
149
+ luke_slider = gr.Slider(0, 10, value=0, step=1, label="Luke encounters this week")
150
+ rey_slider = gr.Slider(0, 10, value=0, step=1, label="Rey encounters this week")
151
+
152
+ plot = gr.Plot()
153
+ output = gr.Markdown()
154
+
155
+ luke_slider.change(predict, [luke_slider, rey_slider], [plot, output])
156
+ rey_slider.change(predict, [luke_slider, rey_slider], [plot, output])
157
+
158
+ demo.load(predict, [luke_slider, rey_slider], [plot, output])
159
+
160
+ gr.Markdown(f"""---
161
+ **Learned B:**
162
+ ```
163
+ {np.array2string(B_learned, precision=2)}
164
+ ```
165
+ """)
166
+
167
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio
2
+ numpy<2
3
+ torch
4
+ plotly