arunalk722 commited on
Commit
1c2f068
·
verified ·
1 Parent(s): f358426

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +222 -0
  2. deploy.py +36 -0
  3. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import pandas as pd
5
+ import numpy as np
6
+ import pickle
7
+ import re
8
+ import gradio as gr
9
+ from transformers import DebertaV2Model, DebertaV2Tokenizer
10
+ from sklearn.preprocessing import StandardScaler
11
+
12
+ # ==========================
13
+ # Configuration
14
+ # ==========================
15
+ DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
16
+ MAX_LENGTH = 256
17
+ MODELS_DIR = './models/'
18
+ CAT_ENCODER_PATH = os.path.join(MODELS_DIR, 'cat_encoder.pkl')
19
+ MISC_ENCODER_PATH = os.path.join(MODELS_DIR, 'misc_encoder.pkl')
20
+ FEATURE_COLS_PATH = os.path.join(MODELS_DIR, 'feature_cols.pkl')
21
+ TRAIN_DATA_PATH = './dataset/train.csv'
22
+ DEFAULT_MODEL = 'map_2025_best_model_fold7.pt'
23
+
24
+ # ==========================
25
+ # Feature Extraction (from training script)
26
+ # ==========================
27
+ def extract_math_features(text):
28
+ if not isinstance(text, str):
29
+ return {
30
+ 'frac_count': 0, 'number_count': 0, 'operator_count': 0,
31
+ 'decimal_count': 0, 'question_mark': 0, 'math_keyword_count': 0
32
+ }
33
+ features = {
34
+ 'frac_count': len(re.findall(r'FRAC_\d+_\d+|\\frac', text)),
35
+ 'number_count': len(re.findall(r'\b\d+\b', text)),
36
+ 'operator_count': len(re.findall(r'[\+\-\*\/\=]', text)),
37
+ 'decimal_count': len(re.findall(r'\d+\.\d+', text)),
38
+ 'question_mark': int('?' in text),
39
+ 'math_keyword_count': len(re.findall(r'solve|calculate|equation|fraction|decimal', text.lower()))
40
+ }
41
+ return features
42
+
43
+ def create_features(df):
44
+ for col in ['QuestionText', 'MC_Answer', 'StudentExplanation']:
45
+ df[col] = df[col].fillna('')
46
+ df['mc_answer_len'] = df['MC_Answer'].str.len()
47
+ df['explanation_len'] = df['StudentExplanation'].str.len()
48
+ df['question_len'] = df['QuestionText'].str.len()
49
+ df['explanation_to_question_ratio'] = df['explanation_len'] / (df['question_len'] + 1)
50
+ for col in ['QuestionText', 'MC_Answer', 'StudentExplanation']:
51
+ mf = df[col].apply(extract_math_features).apply(pd.Series)
52
+ prefix = 'mc_' if col == 'MC_Answer' else 'exp_' if col == 'StudentExplanation' else ''
53
+ mf.columns = [f'{prefix}{c}' for c in mf.columns]
54
+ df = pd.concat([df, mf], axis=1)
55
+ df['sentence'] = (
56
+ "Question: " + df['QuestionText'] +
57
+ " Answer: " + df['MC_Answer'] +
58
+ " Explanation: " + df['StudentExplanation']
59
+ )
60
+ return df
61
+
62
+ # ==========================
63
+ # Deep Learning Model (from training script)
64
+ # ==========================
65
+ class MathMisconceptionModel(nn.Module):
66
+ def __init__(self, n_categories, n_misconceptions, feature_dim):
67
+ super().__init__()
68
+ self.bert = DebertaV2Model.from_pretrained('microsoft/deberta-v3-small')
69
+ self.tokenizer = DebertaV2Tokenizer.from_pretrained('microsoft/deberta-v3-small')
70
+ self.feature_processor = nn.Sequential(
71
+ nn.Linear(feature_dim, 64),
72
+ nn.ReLU(),
73
+ nn.Dropout(0.3)
74
+ )
75
+ self.category_head = nn.Sequential(
76
+ nn.Linear(768 + 64, 256),
77
+ nn.ReLU(),
78
+ nn.Dropout(0.2),
79
+ nn.Linear(256, n_categories)
80
+ )
81
+ self.misconception_head = nn.Sequential(
82
+ nn.Linear(768 + 64, 256),
83
+ nn.ReLU(),
84
+ nn.Dropout(0.2),
85
+ nn.Linear(256, n_misconceptions)
86
+ )
87
+
88
+ def forward(self, input_texts, features):
89
+ tokens = self.tokenizer(
90
+ input_texts,
91
+ padding=True,
92
+ truncation=True,
93
+ max_length=MAX_LENGTH,
94
+ return_tensors="pt"
95
+ ).to(DEVICE)
96
+ outputs = self.bert(**tokens)
97
+ text_emb = outputs.last_hidden_state[:, 0, :]
98
+ feat_emb = self.feature_processor(features)
99
+ combined = torch.cat([text_emb, feat_emb], dim=1)
100
+ return self.category_head(combined), self.misconception_head(combined)
101
+
102
+ # ==========================
103
+ # Load Resources
104
+ # ==========================
105
+ try:
106
+ with open(CAT_ENCODER_PATH, 'rb') as f:
107
+ cat_enc = pickle.load(f)
108
+ with open(MISC_ENCODER_PATH, 'rb') as f:
109
+ misc_enc = pickle.load(f)
110
+ with open(FEATURE_COLS_PATH, 'rb') as f:
111
+ feature_cols = pickle.load(f)
112
+
113
+ # Fit scaler on the original training data
114
+ train_df = pd.read_csv(TRAIN_DATA_PATH)
115
+ processed_train_df = create_features(train_df.copy())
116
+ for col in feature_cols:
117
+ if col not in processed_train_df.columns:
118
+ processed_train_df[col] = 0
119
+ train_features = processed_train_df[feature_cols].fillna(0).values
120
+ scaler = StandardScaler().fit(train_features)
121
+
122
+ except FileNotFoundError as e:
123
+ print(f"Error loading resources: {e}")
124
+ exit()
125
+
126
+ # ==========================
127
+ # Prediction Logic
128
+ # ==========================
129
+ def predict(model_name, question, mc_answer, explanation, export_csv):
130
+ model_path = os.path.join(MODELS_DIR, model_name)
131
+ if not os.path.exists(model_path):
132
+ return "Model not found.", None
133
+
134
+ # Create DataFrame for prediction
135
+ data = {
136
+ 'QuestionText': [question],
137
+ 'MC_Answer': [mc_answer],
138
+ 'StudentExplanation': [explanation]
139
+ }
140
+ df = pd.DataFrame(data)
141
+
142
+ # Feature engineering
143
+ processed_df = create_features(df.copy())
144
+ for col in feature_cols:
145
+ if col not in processed_df.columns:
146
+ processed_df[col] = 0
147
+ features = processed_df[feature_cols].fillna(0).values
148
+ features_scaled = scaler.transform(features)
149
+
150
+ # Load model
151
+ model = MathMisconceptionModel(
152
+ n_categories=len(cat_enc.classes_),
153
+ n_misconceptions=len(misc_enc.classes_),
154
+ feature_dim=features_scaled.shape[1]
155
+ ).to(DEVICE)
156
+ model.load_state_dict(torch.load(model_path, map_location=DEVICE))
157
+ model.eval()
158
+
159
+ # Prediction
160
+ text = processed_df['sentence'].tolist()
161
+ features_tensor = torch.tensor(features_scaled, dtype=torch.float).to(DEVICE)
162
+
163
+ with torch.no_grad():
164
+ cat_logits, misc_logits = model(text, features_tensor)
165
+ cat_pred = torch.argmax(cat_logits, 1).cpu().item()
166
+ misc_pred = torch.argmax(misc_logits, 1).cpu().item()
167
+
168
+ predicted_category = cat_enc.inverse_transform([cat_pred])[0]
169
+ predicted_misconception = misc_enc.inverse_transform([misc_pred])[0]
170
+
171
+ result_text = (
172
+ f"Predicted Category: {predicted_category}\n"
173
+ f"Predicted Misconception: {predicted_misconception}"
174
+ )
175
+
176
+ csv_path = None
177
+ if export_csv:
178
+ export_df = pd.DataFrame([{
179
+ "Question": question,
180
+ "MC_Answer": mc_answer,
181
+ "Student_Explanation": explanation,
182
+ "Predicted_Category": predicted_category,
183
+ "Predicted_Misconception": predicted_misconception,
184
+ "Model_Used": model_name
185
+ }])
186
+ csv_path = "predictions.csv"
187
+ file_exists = os.path.isfile(csv_path)
188
+ export_df.to_csv(csv_path, mode='a', header=not file_exists, index=False)
189
+
190
+ return result_text, csv_path
191
+
192
+ # ==========================
193
+ # Gradio UI
194
+ # ==========================
195
+ model_files = [f for f in os.listdir(MODELS_DIR) if f.endswith('.pt')]
196
+
197
+ iface = gr.Interface(
198
+ fn=predict,
199
+ inputs=[
200
+ gr.Dropdown(model_files, value=DEFAULT_MODEL, label="Select Model"),
201
+ gr.Textbox(label="Enter Question", lines=3),
202
+ gr.Textbox(label="Enter Correct Answer (MC_Answer)", lines=1),
203
+ gr.Textbox(label="Enter Student's Explanation", lines=5),
204
+ gr.Checkbox(label="Export Prediction to CSV")
205
+ ],
206
+ outputs=[
207
+ gr.Textbox(label="Prediction Result"),
208
+ gr.File(label="CSV File (if exported)")
209
+ ],
210
+ title="Math Misconception Predictor",
211
+ description="Select a model and provide the question, correct answer, and student's explanation to get a prediction.",
212
+ theme=gr.themes.Soft()
213
+ )
214
+
215
+ if __name__ == "__main__":
216
+ iface.launch(
217
+ server_name="0.0.0.0", # Allow external connections
218
+ server_port=7860, # Default Gradio port
219
+ share=True, # Create public link
220
+ debug=False, # Disable debug mode for production
221
+ show_error=True # Show errors to users
222
+ )
deploy.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ from app import iface
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser(description='Deploy Math Misconception Predictor')
7
+ parser.add_argument('--host', type=str, default='0.0.0.0', help='Host to bind to')
8
+ parser.add_argument('--port', type=int, default=7860, help='Port to bind to')
9
+ parser.add_argument('--share', action='store_true', default=True, help='Create public link')
10
+ parser.add_argument('--debug', action='store_true', default=False, help='Enable debug mode')
11
+ parser.add_argument('--ssl-keyfile', type=str, help='SSL key file for HTTPS')
12
+ parser.add_argument('--ssl-certfile', type=str, help='SSL certificate file for HTTPS')
13
+
14
+ args = parser.parse_args()
15
+
16
+ print(f"Starting Math Misconception Predictor...")
17
+ print(f"Host: {args.host}")
18
+ print(f"Port: {args.port}")
19
+ print(f"Public Link: {'Yes' if args.share else 'No'}")
20
+ print(f"Debug Mode: {'Yes' if args.debug else 'No'}")
21
+
22
+ # Launch the app
23
+ iface.launch(
24
+ server_name=args.host,
25
+ server_port=args.port,
26
+ share=args.share,
27
+ debug=args.debug,
28
+ show_error=True,
29
+ ssl_keyfile=args.ssl_keyfile,
30
+ ssl_certfile=args.ssl_certfile,
31
+ auth=None,
32
+ auth_message="Welcome to Math Misconception Predictor!"
33
+ )
34
+
35
+ if __name__ == "__main__":
36
+ main()
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ torch>=2.0.0
2
+ transformers>=4.30.0
3
+ gradio>=4.0.0
4
+ pandas>=1.5.0
5
+ numpy>=1.24.0
6
+ scikit-learn>=1.3.0
7
+ pickle-mixin>=1.0.2