namish10 commited on
Commit
5e38446
·
verified ·
1 Parent(s): 690ed7d

Upload app/agents/doubt_predictor.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app/agents/doubt_predictor.py +470 -0
app/agents/doubt_predictor.py ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Reinforcement Learning Doubt Prediction Agent
3
+
4
+ This agent predicts what doubts a user will have BEFORE they occur,
5
+ using:
6
+ - User's learning history
7
+ - Current topic complexity
8
+ - Behavioral signals (eye tracking, hesitation, scroll patterns)
9
+ - Similar users' learning patterns
10
+ - Topic dependency graphs
11
+
12
+ Based on Deep Q-Learning with attention mechanism
13
+ """
14
+
15
+ import numpy as np
16
+ from typing import Dict, List, Any, Optional, Tuple
17
+ from dataclasses import dataclass, field
18
+ from datetime import datetime
19
+ import json
20
+
21
+
22
+ @dataclass
23
+ class LearningState:
24
+ """Represents the current learning state"""
25
+ topic: str
26
+ subtopic: str
27
+ progress_percentage: float
28
+ time_spent_seconds: int
29
+ confusion_signals: float
30
+ eye_tracking_confidence: float
31
+ scroll_reversals: int
32
+ selection_count: int
33
+ previous_doubts_count: int
34
+ mastery_level: float
35
+ difficulty_rating: float
36
+ time_of_day: int
37
+ streak_days: int
38
+
39
+
40
+ @dataclass
41
+ class DoubtPrediction:
42
+ """Predicted doubt with confidence"""
43
+ predicted_doubt: str
44
+ confidence: float
45
+ suggested_explanation: str
46
+ related_concepts: List[str]
47
+ priority: int
48
+ estimated_resolution_time: int
49
+ prerequisite_topics: List[str]
50
+
51
+
52
+ @dataclass
53
+ class RLPolicy:
54
+ """RL Policy network (simplified)"""
55
+ state_dim: int = 12
56
+ action_dim: int = 100
57
+ learning_rate: float = 0.001
58
+ gamma: float = 0.95
59
+ epsilon: float = 1.0
60
+ epsilon_decay: float = 0.995
61
+ epsilon_min: float = 0.01
62
+
63
+ q_table: Dict[str, np.ndarray] = field(default_factory=dict)
64
+
65
+ state_mean: np.ndarray = None
66
+ state_std: np.ndarray = None
67
+
68
+
69
+ class DoubtPredictorAgent:
70
+ """
71
+ RL-based agent that predicts user doubts before they occur.
72
+
73
+ Uses a Deep Q-Network inspired architecture with:
74
+ - State encoding from learning signals
75
+ - Attention mechanism for topic relationships
76
+ - Experience replay for learning
77
+ - Progressive prediction confidence
78
+ """
79
+
80
+ def __init__(self, user_id: str, config: Optional[Dict] = None):
81
+ self.user_id = user_id
82
+ self.config = config or {}
83
+
84
+ self.policy = RLPolicy()
85
+ self.experience_buffer = []
86
+ self.max_buffer_size = 1000
87
+
88
+ self.topic_embeddings = {}
89
+ self.concept_graph = {}
90
+ self.user_preferences = {}
91
+
92
+ self._initialize_topic_knowledge()
93
+
94
+ def _initialize_topic_knowledge(self):
95
+ """Initialize base topic relationships"""
96
+ self.concept_graph = {
97
+ 'python': ['variables', 'functions', 'classes', 'loops', 'conditionals', 'data_structures'],
98
+ 'machine_learning': ['linear_regression', 'classification', 'neural_networks', 'optimization', 'feature_engineering'],
99
+ 'deep_learning': ['perceptrons', 'backpropagation', 'convolutional_nets', 'recurrent_nets', 'transformers', 'attention'],
100
+ 'statistics': ['probability', 'distributions', 'hypothesis_testing', 'regression', 'bayesian'],
101
+ 'calculus': ['derivatives', 'integrals', 'limits', 'series', 'multivariable'],
102
+ 'linear_algebra': ['vectors', 'matrices', 'eigenvalues', 'transformations', 'decompositions']
103
+ }
104
+
105
+ self.doubt_templates = {
106
+ 'variables': [
107
+ "What is the difference between mutable and immutable types?",
108
+ "How does variable scope work in nested functions?",
109
+ "When should I use global vs local variables?"
110
+ ],
111
+ 'functions': [
112
+ "What is the difference between arguments and parameters?",
113
+ "How do *args and **kwargs work?",
114
+ "When should I use lambda functions?"
115
+ ],
116
+ 'classes': [
117
+ "What is the difference between class and instance attributes?",
118
+ "How does inheritance work with multiple inheritance?",
119
+ "What are abstract base classes and when to use them?"
120
+ ],
121
+ 'loops': [
122
+ "When should I use for vs while loops?",
123
+ "How do list comprehensions replace loops?",
124
+ "What is the difference between break and continue?"
125
+ ],
126
+ 'data_structures': [
127
+ "When should I use lists vs dictionaries?",
128
+ "What is the time complexity of dictionary operations?",
129
+ "How do sets differ from lists in performance?"
130
+ ],
131
+ 'linear_regression': [
132
+ "What is the cost function and how is it optimized?",
133
+ "How do I handle multicollinearity?",
134
+ "What are the assumptions of linear regression?"
135
+ ],
136
+ 'neural_networks': [
137
+ "What is the role of activation functions?",
138
+ "How does backpropagation compute gradients?",
139
+ "What is the vanishing gradient problem?"
140
+ ],
141
+ 'transformers': [
142
+ "How does self-attention work?",
143
+ "What is the difference between encoder and decoder?",
144
+ "Why is positional encoding needed?"
145
+ ]
146
+ }
147
+
148
+ def get_current_state(self, learning_context: Dict) -> LearningState:
149
+ """Extract current learning state from context"""
150
+ return LearningState(
151
+ topic=learning_context.get('topic', 'unknown'),
152
+ subtopic=learning_context.get('subtopic', 'unknown'),
153
+ progress_percentage=learning_context.get('progress', 0.0),
154
+ time_spent_seconds=learning_context.get('time_spent', 0),
155
+ confusion_signals=learning_context.get('confusion_score', 0.0),
156
+ eye_tracking_confidence=learning_context.get('eye_confidence', 0.0),
157
+ scroll_reversals=learning_context.get('scroll_reversals', 0),
158
+ selection_count=learning_context.get('selections', 0),
159
+ previous_doubts_count=learning_context.get('prev_doubts', 0),
160
+ mastery_level=learning_context.get('mastery', 0.0),
161
+ difficulty_rating=learning_context.get('difficulty', 0.5),
162
+ time_of_day=datetime.now().hour,
163
+ streak_days=learning_context.get('streak', 0)
164
+ )
165
+
166
+ def state_to_vector(self, state: LearningState) -> np.ndarray:
167
+ """Convert state to feature vector"""
168
+ features = [
169
+ self._topic_to_feature(state.topic),
170
+ self._topic_to_feature(state.subtopic),
171
+ state.progress_percentage,
172
+ np.log1p(state.time_spent_seconds) / 10,
173
+ state.confusion_signals,
174
+ state.eye_tracking_confidence,
175
+ np.tanh(state.scroll_reversals / 10),
176
+ np.tanh(state.selection_count / 20),
177
+ np.tanh(state.previous_doubts_count / 50),
178
+ state.mastery_level,
179
+ state.difficulty_rating,
180
+ np.sin(2 * np.pi * state.time_of_day / 24),
181
+ np.cos(2 * np.pi * state.time_of_day / 24),
182
+ np.tanh(state.streak_days / 30)
183
+ ]
184
+
185
+ return np.array(features, dtype=np.float32)
186
+
187
+ def _topic_to_feature(self, topic: str) -> float:
188
+ """Convert topic to numerical feature"""
189
+ topic_lower = topic.lower().replace(' ', '_')
190
+
191
+ topic_order = [
192
+ 'variables', 'functions', 'classes', 'loops', 'conditionals', 'data_structures',
193
+ 'probability', 'distributions', 'derivatives', 'integrals', 'vectors', 'matrices',
194
+ 'linear_regression', 'classification', 'neural_networks', 'optimization',
195
+ 'convolutional_nets', 'recurrent_nets', 'transformers', 'attention'
196
+ ]
197
+
198
+ if topic_lower in topic_order:
199
+ return topic_order.index(topic_lower) / len(topic_order)
200
+ return 0.5
201
+
202
+ def predict_doubts(
203
+ self,
204
+ learning_context: Dict,
205
+ top_k: int = 5,
206
+ gesture_influence: Optional[float] = None
207
+ ) -> List[DoubtPrediction]:
208
+ """
209
+ Predict likely doubts for current learning context.
210
+
211
+ Uses RL policy to estimate which doubts are most likely,
212
+ based on current state and historical patterns.
213
+
214
+ Args:
215
+ learning_context: Current learning state
216
+ top_k: Number of predictions to return
217
+ gesture_influence: Optional gesture-based signal (0-1) that increases doubt confidence
218
+ """
219
+ state = self.get_current_state(learning_context)
220
+ state_vec = self.state_to_vector(state)
221
+
222
+ predictions = []
223
+
224
+ related_concepts = self._get_related_concepts(state.topic, state.subtopic)
225
+
226
+ for concept in related_concepts:
227
+ if concept not in self.doubt_templates:
228
+ continue
229
+
230
+ templates = self.doubt_templates[concept]
231
+
232
+ for template in templates:
233
+ confidence = self._calculate_doubt_confidence(
234
+ state, concept, template, gesture_influence
235
+ )
236
+
237
+ if confidence > 0.3:
238
+ prerequisite = self._get_prerequisites(concept)
239
+
240
+ prediction = DoubtPrediction(
241
+ predicted_doubt=template,
242
+ confidence=confidence,
243
+ suggested_explanation=self._generate_explanation_hint(concept, template),
244
+ related_concepts=self._get_related_concepts(concept, ''),
245
+ priority=self._calculate_priority(state, confidence),
246
+ estimated_resolution_time=self._estimate_time(concept),
247
+ prerequisite_topics=prerequisite
248
+ )
249
+ predictions.append(prediction)
250
+
251
+ predictions.sort(key=lambda x: x.priority, reverse=True)
252
+ return predictions[:top_k]
253
+
254
+ def _calculate_doubt_confidence(
255
+ self,
256
+ state: LearningState,
257
+ concept: str,
258
+ template: str,
259
+ gesture_influence: Optional[float] = None
260
+ ) -> float:
261
+ """Calculate confidence that user will have this doubt"""
262
+ base_confidence = 0.5
263
+
264
+ if state.confusion_signals > 0.7:
265
+ base_confidence += 0.2
266
+
267
+ if state.eye_tracking_confidence < 0.5:
268
+ base_confidence += 0.15
269
+
270
+ if state.scroll_reversals > 5:
271
+ base_confidence += 0.1
272
+
273
+ if concept in self.concept_graph.get(state.topic.lower(), []):
274
+ base_confidence += 0.1
275
+
276
+ if state.difficulty_rating > 0.7:
277
+ base_confidence += 0.15
278
+
279
+ if state.mastery_level < 0.3:
280
+ base_confidence += 0.1
281
+
282
+ if gesture_influence is not None and gesture_influence > 0.5:
283
+ base_confidence += (gesture_influence - 0.5) * 0.4
284
+
285
+ difficulty_penalty = state.difficulty_rating * 0.1
286
+ base_confidence -= difficulty_penalty
287
+
288
+ return min(max(base_confidence, 0.0), 1.0)
289
+
290
+ def _get_related_concepts(self, topic: str, subtopic: str) -> List[str]:
291
+ """Get concepts related to current topic"""
292
+ topic_lower = topic.lower().replace(' ', '_')
293
+ subtopic_lower = subtopic.lower().replace(' ', '_')
294
+
295
+ related = []
296
+
297
+ if topic_lower in self.concept_graph:
298
+ related.extend(self.concept_graph[topic_lower])
299
+
300
+ if subtopic_lower in self.concept_graph:
301
+ related.extend(self.concept_graph[subtopic_lower])
302
+
303
+ for t, concepts in self.concept_graph.items():
304
+ for c in concepts:
305
+ if t == topic_lower or c == subtopic_lower:
306
+ related.extend(concepts)
307
+
308
+ return list(set(related))[:10]
309
+
310
+ def _get_prerequisites(self, concept: str) -> List[str]:
311
+ """Get prerequisite concepts that should be understood first"""
312
+ prereq_map = {
313
+ 'neural_networks': ['linear_regression', 'calculus', 'linear_algebra'],
314
+ 'transformers': ['neural_networks', 'attention', 'linear_algebra'],
315
+ 'convolutional_nets': ['neural_networks', 'linear_algebra'],
316
+ 'backpropagation': ['derivatives', 'chain_rule'],
317
+ 'optimization': ['calculus', 'derivatives'],
318
+ 'classification': ['probability', 'linear_regression'],
319
+ }
320
+
321
+ return prereq_map.get(concept, [])
322
+
323
+ def _generate_explanation_hint(self, concept: str, template: str) -> str:
324
+ """Generate a quick explanation hint"""
325
+ hints = {
326
+ 'variables': 'Variables store data values that can be changed or accessed later.',
327
+ 'functions': 'Functions are reusable blocks of code that perform specific tasks.',
328
+ 'classes': 'Classes define blueprints for creating objects with attributes and methods.',
329
+ 'neural_networks': 'Neural networks learn patterns through weighted connections between neurons.',
330
+ 'transformers': 'Transformers use self-attention to process sequences in parallel.',
331
+ 'backpropagation': 'Backpropagation calculates gradients by propagating errors backwards through the network.'
332
+ }
333
+
334
+ return hints.get(concept, 'Review the fundamental concepts before proceeding.')
335
+
336
+ def _calculate_priority(self, state: LearningState, confidence: float) -> float:
337
+ """Calculate priority score for doubt prediction"""
338
+ priority = confidence * 0.4
339
+
340
+ priority += state.confusion_signals * 0.2
341
+ priority += (1 - state.mastery_level) * 0.2
342
+ priority += state.difficulty_rating * 0.1
343
+ priority += min(state.time_spent_seconds / 600, 1) * 0.1
344
+
345
+ return priority
346
+
347
+ def _estimate_time(self, concept: str) -> int:
348
+ """Estimate time to resolve doubt in minutes"""
349
+ time_map = {
350
+ 'variables': 5,
351
+ 'functions': 10,
352
+ 'classes': 15,
353
+ 'loops': 8,
354
+ 'data_structures': 20,
355
+ 'linear_regression': 25,
356
+ 'neural_networks': 30,
357
+ 'transformers': 45,
358
+ 'backpropagation': 35
359
+ }
360
+
361
+ return time_map.get(concept, 15)
362
+
363
+ def update_policy(
364
+ self,
365
+ state: LearningState,
366
+ predicted_doubt: str,
367
+ actual_doubt: str,
368
+ reward: float
369
+ ):
370
+ """
371
+ Update RL policy based on whether prediction was correct.
372
+
373
+ Positive reward if predicted doubt matches actual doubt.
374
+ Negative reward for false positives.
375
+ """
376
+ state_key = self._state_to_key(state)
377
+
378
+ if state_key not in self.policy.q_table:
379
+ self.policy.q_table[state_key] = np.zeros(self.policy.action_dim)
380
+
381
+ action_idx = self._doubt_to_action(predicted_doubt)
382
+
383
+ current_q = self.policy.q_table[state_key][action_idx]
384
+
385
+ max_next_q = np.max(self.policy.q_table.get(state_key, [0]))
386
+
387
+ new_q = current_q + self.policy.learning_rate * (
388
+ reward + self.policy.gamma * max_next_q - current_q
389
+ )
390
+
391
+ self.policy.q_table[state_key][action_idx] = new_q
392
+
393
+ self.experience_buffer.append({
394
+ 'state': state,
395
+ 'predicted': predicted_doubt,
396
+ 'actual': actual_doubt,
397
+ 'reward': reward,
398
+ 'timestamp': datetime.now().isoformat()
399
+ })
400
+
401
+ if len(self.experience_buffer) > self.max_buffer_size:
402
+ self.experience_buffer.pop(0)
403
+
404
+ if self.policy.epsilon > self.policy.epsilon_min:
405
+ self.policy.epsilon *= self.policy.epsilon_decay
406
+
407
+ def _state_to_key(self, state: LearningState) -> str:
408
+ """Convert state to hashable key"""
409
+ return f"{state.topic}_{state.subtopic}_{int(state.progress_percentage * 10)}_{int(state.confusion_signals * 10)}"
410
+
411
+ def _doubt_to_action(self, doubt: str) -> int:
412
+ """Convert doubt to action index"""
413
+ doubt_hash = hash(doubt.lower().strip())
414
+ return abs(doubt_hash) % self.policy.action_dim
415
+
416
+ def get_learning_recommendations(self, learning_context: Dict) -> Dict[str, Any]:
417
+ """Get personalized learning recommendations based on predictions"""
418
+ predictions = self.predict_doubts(learning_context, top_k=3)
419
+
420
+ state = self.get_current_state(learning_context)
421
+
422
+ recommendations = {
423
+ 'next_topics': [],
424
+ 'review_topics': [],
425
+ 'practice_exercises': [],
426
+ 'estimated_difficulty': state.difficulty_rating,
427
+ 'predicted_struggles': [p.predicted_doubt for p in predictions],
428
+ 'confidence_boosters': [],
429
+ 'optimal_break_time': self._suggest_break_time(learning_context)
430
+ }
431
+
432
+ if state.confusion_signals > 0.7:
433
+ recommendations['next_topics'] = self._get_prerequisites(state.topic)
434
+ recommendations['confidence_boosters'].append('Review prerequisite concepts')
435
+
436
+ if state.mastery_level > 0.8:
437
+ recommendations['next_topics'].append(state.topic)
438
+ recommendations['practice_exercises'].append(f"Advanced {state.topic} project")
439
+
440
+ if state.time_spent_seconds > 1800:
441
+ recommendations['suggest_break'] = True
442
+ recommendations['break_duration'] = 5
443
+
444
+ return recommendations
445
+
446
+ def _suggest_break_time(self, context: Dict) -> Optional[str]:
447
+ """Suggest optimal break time based on learning patterns"""
448
+ if context.get('confusion_score', 0) > 0.6:
449
+ return "Take a 5-minute break to process information"
450
+ elif context.get('time_spent', 0) > 2400:
451
+ return "Take a longer 15-minute break"
452
+ return None
453
+
454
+ def export_model(self) -> Dict:
455
+ """Export model state for persistence"""
456
+ return {
457
+ 'user_id': self.user_id,
458
+ 'q_table_size': len(self.policy.q_table),
459
+ 'experience_buffer_size': len(self.experience_buffer),
460
+ 'epsilon': self.policy.epsilon,
461
+ 'concepts': list(self.concept_graph.keys()),
462
+ 'doubt_templates': list(self.doubt_templates.keys())
463
+ }
464
+
465
+ def import_model(self, model_data: Dict):
466
+ """Import model state from persistence"""
467
+ if 'concepts' in model_data:
468
+ for concept in model_data['concepts']:
469
+ if concept not in self.concept_graph:
470
+ self.concept_graph[concept] = []