ananyakarn commited on
Commit
d4fbacf
·
verified ·
1 Parent(s): 05a11f8

added attention model also

Browse files
Files changed (1) hide show
  1. app.py +117 -99
app.py CHANGED
@@ -7,75 +7,46 @@ np.random.seed(42)
7
  random.seed(42)
8
 
9
  # =========================
10
- # 1. SAFE DOWNLOAD
11
  # =========================
12
- import os, requests, zipfile, time
 
 
 
 
 
 
13
 
14
- def safe_download(url, path):
15
- if os.path.exists(path):
16
- return
17
- for _ in range(3):
18
- try:
19
- r = requests.get(url, stream=True, timeout=60)
20
- with open(path, "wb") as f:
21
- for chunk in r.iter_content(8192):
22
- if chunk:
23
- f.write(chunk)
24
- return
25
- except:
26
- time.sleep(3)
27
- raise Exception("Download failed")
28
 
29
  # =========================
30
  # 2. LOAD LABELS
31
  # =========================
32
- import pandas as pd
33
-
34
  def load_split(csv_file):
35
  df = pd.read_csv(csv_file)
36
  df.columns = df.columns.str.strip()
37
  split = {}
 
38
  for _, row in df.iterrows():
39
  try:
40
  pid = str(int(row["Participant_ID"]))
41
  split[pid] = int(row["PHQ8_Binary"])
42
  except:
43
  continue
 
44
  return split
45
 
46
  train_labels = load_split("train_split_Depression_AVEC2017.csv")
47
  dev_labels = load_split("dev_split_Depression_AVEC2017.csv")
48
 
49
- REQUIRED_IDS = set(list(train_labels.keys()) + list(dev_labels.keys()))
50
-
51
  # =========================
52
- # 3. DOWNLOAD + EXTRACT
53
  # =========================
54
- urls = [
55
- "https://huggingface.co/datasets/ananyakarn/DAIC_WOZ_Data/resolve/main/DAIC_WOZ_Data.zip",
56
- "https://huggingface.co/datasets/ananyakarn/DAIC_WOZ_Data/resolve/main/DAIC_WOZ_Data-2.zip"
57
- ]
58
-
59
- os.makedirs("data", exist_ok=True)
60
 
61
- if len(os.listdir("data")) == 0:
62
- for i, url in enumerate(urls):
63
- zip_path = f"temp_{i}.zip"
64
- safe_download(url, zip_path)
65
-
66
- with zipfile.ZipFile(zip_path, "r") as z:
67
- for file in z.namelist():
68
- if any(pid in file for pid in REQUIRED_IDS):
69
- z.extract(file, "data")
70
-
71
- os.remove(zip_path)
72
-
73
- # =========================
74
- # 4. GET PATHS
75
- # =========================
76
  def get_paths():
77
  paths = []
78
- for root, dirs, _ in os.walk("data"):
79
  for d in dirs:
80
  if "_P" in d or "_C" in d:
81
  paths.append(os.path.join(root, d))
@@ -83,39 +54,23 @@ def get_paths():
83
 
84
  ALL_PATHS = get_paths()
85
 
86
- # FILTER VALID PARTICIPANTS (IMPORTANT FIX)
87
  ALL_PATHS = [
88
  p for p in ALL_PATHS
89
- if os.path.basename(p).split("_")[0] in REQUIRED_IDS
90
  ]
91
 
92
- print("Usable participants:", len(ALL_PATHS))
93
-
94
- if len(ALL_PATHS) < 10:
95
- raise Exception("Too few participants extracted!")
96
-
97
- # =========================
98
- # 5. LIBRARIES
99
- # =========================
100
- import librosa
101
- import torch.nn as nn
102
- from tqdm import tqdm
103
- from transformers import AutoTokenizer, AutoModel
104
- from sklearn.preprocessing import StandardScaler
105
- from sklearn.metrics import accuracy_score, f1_score
106
- from sklearn.ensemble import RandomForestClassifier
107
-
108
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
109
 
110
  # =========================
111
- # 6. BERT
112
  # =========================
113
  tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
114
  bert = AutoModel.from_pretrained("distilbert-base-uncased").to(device)
115
  bert.eval()
116
 
117
  # =========================
118
- # 7. FEATURES
119
  # =========================
120
  def load_text(folder):
121
  try:
@@ -133,22 +88,26 @@ def get_text_embedding(text):
133
  truncation=True, padding=True, max_length=128).to(device)
134
  with torch.no_grad():
135
  out = bert(**inputs)
 
136
  return out.last_hidden_state.mean(dim=1).squeeze().cpu().numpy()
137
 
138
  def get_audio(folder):
139
  try:
140
  file = [f for f in os.listdir(folder) if f.endswith("_AUDIO.wav")][0]
141
  y, sr = librosa.load(os.path.join(folder, file), sr=16000)
142
- return np.mean(librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40).T, axis=0)
 
143
  except:
144
  return np.zeros(40)
145
 
146
  def get_visual(folder):
147
  feats = []
 
148
  for key in ["AUs", "pose", "gaze"]:
149
  try:
150
  file = [f for f in os.listdir(folder) if key in f][0]
151
  df = pd.read_csv(os.path.join(folder, file))
 
152
  df = df.select_dtypes(include=[np.number])
153
  df.replace(-100, np.nan, inplace=True)
154
  df.fillna(0, inplace=True)
@@ -156,16 +115,18 @@ def get_visual(folder):
156
  feats.append(np.concatenate([df.mean().values, df.std().values]))
157
  except:
158
  feats.append(np.zeros(20))
 
159
  return np.concatenate(feats)
160
 
161
  # =========================
162
- # 8. BUILD DATA
163
  # =========================
164
  def build(labels):
165
  Xt, Xa, Xv, y = [], [], [], []
166
 
167
  for p in tqdm(ALL_PATHS):
168
  pid = os.path.basename(p).split("_")[0]
 
169
  if pid not in labels:
170
  continue
171
 
@@ -182,12 +143,8 @@ Xt_test, Xa_test, Xv_test, y_test = build(dev_labels)
182
  print("Train size:", len(y_train))
183
  print("Test size:", len(y_test))
184
 
185
- # ✅ SAFETY CHECK
186
- if len(set(y_train)) < 2 or len(set(y_test)) < 2:
187
- raise Exception("Dataset has only one class!")
188
-
189
  # =========================
190
- # 9. NORMALIZATION
191
  # =========================
192
  sc_t, sc_a, sc_v = StandardScaler(), StandardScaler(), StandardScaler()
193
 
@@ -200,12 +157,18 @@ Xa_test = sc_a.transform(Xa_test)
200
  Xv_test = sc_v.transform(Xv_test)
201
 
202
  # =========================
203
- # 10. RANDOM FOREST
204
  # =========================
205
- X_train_rf = np.concatenate([Xt, Xa, Xv], axis=1)
206
- X_test_rf = np.concatenate([Xt_test, Xa_test, Xv_test], axis=1)
207
 
208
- rf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
 
 
 
 
 
 
209
 
210
  print("\nTraining Random Forest...")
211
  rf.fit(X_train_rf, y_train)
@@ -216,60 +179,115 @@ rf_acc = accuracy_score(y_test, rf_preds)
216
  rf_f1 = f1_score(y_test, rf_preds)
217
 
218
  # =========================
219
- # 11. TORCH MODEL
220
  # =========================
221
- Xt = torch.tensor(Xt, dtype=torch.float32).to(device)
222
- Xa = torch.tensor(Xa, dtype=torch.float32).to(device)
223
- Xv = torch.tensor(Xv, dtype=torch.float32).to(device)
224
  yt = torch.tensor(y_train, dtype=torch.float32).to(device)
225
 
226
- Xt_test = torch.tensor(Xt_test, dtype=torch.float32).to(device)
227
- Xa_test = torch.tensor(Xa_test, dtype=torch.float32).to(device)
228
- Xv_test = torch.tensor(Xv_test, dtype=torch.float32).to(device)
229
 
230
- class Simple(nn.Module):
 
 
 
231
  def __init__(self, v):
232
  super().__init__()
233
  self.net = nn.Sequential(
234
  nn.Linear(768+40+v,128),
235
  nn.ReLU(),
 
236
  nn.Linear(128,1)
237
  )
238
-
239
  def forward(self,t,a,v):
240
  return self.net(torch.cat([t,a,v],1))
241
 
242
- model = Simple(Xv.shape[1]).to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- opt = torch.optim.Adam(model.parameters(), lr=1e-4)
245
  loss_fn = nn.BCEWithLogitsLoss()
246
 
247
- print("\nTraining Neural Model...")
 
 
 
 
 
 
248
 
249
- for epoch in range(5):
250
- opt.zero_grad()
251
- loss = loss_fn(model(Xt,Xa,Xv).squeeze(), yt)
 
252
  loss.backward()
253
- opt.step()
254
- print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
255
 
256
  # =========================
257
  # 12. EVALUATION
258
  # =========================
259
  with torch.no_grad():
260
- preds = (torch.sigmoid(
261
- model(Xt_test,Xa_test,Xv_test).squeeze()
262
- ) > 0.5).int().cpu().numpy()
263
 
264
  # =========================
265
  # 13. RESULTS
266
  # =========================
267
- print("\n===== FINAL RESULTS =====")
268
 
269
  print("\nRandom Forest:")
270
  print("Accuracy:", rf_acc)
271
  print("F1:", rf_f1)
272
 
273
- print("\nNeural Model:")
274
- print("Accuracy:", accuracy_score(y_test, preds))
275
- print("F1:", f1_score(y_test, preds))
 
 
 
 
 
7
  random.seed(42)
8
 
9
  # =========================
10
+ # 1. IMPORTS
11
  # =========================
12
+ import os, pandas as pd, librosa
13
+ import torch.nn as nn
14
+ from tqdm import tqdm
15
+ from transformers import AutoTokenizer, AutoModel
16
+ from sklearn.preprocessing import StandardScaler
17
+ from sklearn.metrics import accuracy_score, f1_score
18
+ from sklearn.ensemble import RandomForestClassifier
19
 
20
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  # =========================
23
  # 2. LOAD LABELS
24
  # =========================
 
 
25
  def load_split(csv_file):
26
  df = pd.read_csv(csv_file)
27
  df.columns = df.columns.str.strip()
28
  split = {}
29
+
30
  for _, row in df.iterrows():
31
  try:
32
  pid = str(int(row["Participant_ID"]))
33
  split[pid] = int(row["PHQ8_Binary"])
34
  except:
35
  continue
36
+
37
  return split
38
 
39
  train_labels = load_split("train_split_Depression_AVEC2017.csv")
40
  dev_labels = load_split("dev_split_Depression_AVEC2017.csv")
41
 
 
 
42
  # =========================
43
+ # 3. GET DATA PATHS
44
  # =========================
45
+ DATA_PATH = "data" # 🔥 IMPORTANT: upload dataset manually here
 
 
 
 
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def get_paths():
48
  paths = []
49
+ for root, dirs, _ in os.walk(DATA_PATH):
50
  for d in dirs:
51
  if "_P" in d or "_C" in d:
52
  paths.append(os.path.join(root, d))
 
54
 
55
  ALL_PATHS = get_paths()
56
 
57
+ # Filter valid IDs only
58
  ALL_PATHS = [
59
  p for p in ALL_PATHS
60
+ if os.path.basename(p).split("_")[0] in set(train_labels) | set(dev_labels)
61
  ]
62
 
63
+ print("Total usable participants:", len(ALL_PATHS))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  # =========================
66
+ # 4. LOAD BERT
67
  # =========================
68
  tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
69
  bert = AutoModel.from_pretrained("distilbert-base-uncased").to(device)
70
  bert.eval()
71
 
72
  # =========================
73
+ # 5. FEATURE FUNCTIONS
74
  # =========================
75
  def load_text(folder):
76
  try:
 
88
  truncation=True, padding=True, max_length=128).to(device)
89
  with torch.no_grad():
90
  out = bert(**inputs)
91
+
92
  return out.last_hidden_state.mean(dim=1).squeeze().cpu().numpy()
93
 
94
  def get_audio(folder):
95
  try:
96
  file = [f for f in os.listdir(folder) if f.endswith("_AUDIO.wav")][0]
97
  y, sr = librosa.load(os.path.join(folder, file), sr=16000)
98
+ mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40)
99
+ return np.mean(mfcc.T, axis=0)
100
  except:
101
  return np.zeros(40)
102
 
103
  def get_visual(folder):
104
  feats = []
105
+
106
  for key in ["AUs", "pose", "gaze"]:
107
  try:
108
  file = [f for f in os.listdir(folder) if key in f][0]
109
  df = pd.read_csv(os.path.join(folder, file))
110
+
111
  df = df.select_dtypes(include=[np.number])
112
  df.replace(-100, np.nan, inplace=True)
113
  df.fillna(0, inplace=True)
 
115
  feats.append(np.concatenate([df.mean().values, df.std().values]))
116
  except:
117
  feats.append(np.zeros(20))
118
+
119
  return np.concatenate(feats)
120
 
121
  # =========================
122
+ # 6. BUILD DATASET
123
  # =========================
124
  def build(labels):
125
  Xt, Xa, Xv, y = [], [], [], []
126
 
127
  for p in tqdm(ALL_PATHS):
128
  pid = os.path.basename(p).split("_")[0]
129
+
130
  if pid not in labels:
131
  continue
132
 
 
143
  print("Train size:", len(y_train))
144
  print("Test size:", len(y_test))
145
 
 
 
 
 
146
  # =========================
147
+ # 7. NORMALIZATION
148
  # =========================
149
  sc_t, sc_a, sc_v = StandardScaler(), StandardScaler(), StandardScaler()
150
 
 
157
  Xv_test = sc_v.transform(Xv_test)
158
 
159
  # =========================
160
+ # 8. RANDOM FOREST (IMPROVED)
161
  # =========================
162
+ Xt_rf = Xt[:, :128]
163
+ Xv_rf = Xv[:, :50]
164
 
165
+ Xt_test_rf = Xt_test[:, :128]
166
+ Xv_test_rf = Xv_test[:, :50]
167
+
168
+ X_train_rf = np.concatenate([Xt_rf, Xa, Xv_rf], axis=1)
169
+ X_test_rf = np.concatenate([Xt_test_rf, Xa_test, Xv_test_rf], axis=1)
170
+
171
+ rf = RandomForestClassifier(n_estimators=200, max_depth=5, random_state=42)
172
 
173
  print("\nTraining Random Forest...")
174
  rf.fit(X_train_rf, y_train)
 
179
  rf_f1 = f1_score(y_test, rf_preds)
180
 
181
  # =========================
182
+ # 9. TORCH DATA
183
  # =========================
184
+ Xt_t = torch.tensor(Xt, dtype=torch.float32).to(device)
185
+ Xa_t = torch.tensor(Xa, dtype=torch.float32).to(device)
186
+ Xv_t = torch.tensor(Xv, dtype=torch.float32).to(device)
187
  yt = torch.tensor(y_train, dtype=torch.float32).to(device)
188
 
189
+ Xt_test_t = torch.tensor(Xt_test, dtype=torch.float32).to(device)
190
+ Xa_test_t = torch.tensor(Xa_test, dtype=torch.float32).to(device)
191
+ Xv_test_t = torch.tensor(Xv_test, dtype=torch.float32).to(device)
192
 
193
+ # =========================
194
+ # 10. MODELS
195
+ # =========================
196
+ class SimpleNN(nn.Module):
197
  def __init__(self, v):
198
  super().__init__()
199
  self.net = nn.Sequential(
200
  nn.Linear(768+40+v,128),
201
  nn.ReLU(),
202
+ nn.Dropout(0.3),
203
  nn.Linear(128,1)
204
  )
 
205
  def forward(self,t,a,v):
206
  return self.net(torch.cat([t,a,v],1))
207
 
208
+ class AttentionModel(nn.Module):
209
+ def __init__(self, v):
210
+ super().__init__()
211
+ self.t = nn.Sequential(nn.Linear(768,128), nn.ReLU())
212
+ self.a = nn.Sequential(nn.Linear(40,32), nn.ReLU())
213
+ self.v = nn.Sequential(nn.Linear(v,64), nn.ReLU())
214
+
215
+ self.attn = nn.Sequential(
216
+ nn.Linear(224,64),
217
+ nn.Tanh(),
218
+ nn.Linear(64,3)
219
+ )
220
+
221
+ self.final = nn.Sequential(
222
+ nn.Linear(224,64),
223
+ nn.ReLU(),
224
+ nn.Dropout(0.3),
225
+ nn.Linear(64,1)
226
+ )
227
+
228
+ def forward(self,t,a,v):
229
+ t_feat = self.t(t)
230
+ a_feat = self.a(a)
231
+ v_feat = self.v(v)
232
+
233
+ combined = torch.cat([t_feat,a_feat,v_feat],1)
234
+ weights = torch.softmax(self.attn(combined), dim=1)
235
+
236
+ fused = torch.cat([
237
+ weights[:,0:1]*t_feat,
238
+ weights[:,1:2]*a_feat,
239
+ weights[:,2:3]*v_feat
240
+ ],1)
241
+
242
+ return self.final(fused)
243
+
244
+ # =========================
245
+ # 11. TRAIN MODELS
246
+ # =========================
247
+ baseline = SimpleNN(Xv.shape[1]).to(device)
248
+ attention = AttentionModel(Xv.shape[1]).to(device)
249
+
250
+ opt1 = torch.optim.Adam(baseline.parameters(), lr=1e-4)
251
+ opt2 = torch.optim.AdamW(attention.parameters(), lr=1e-4)
252
 
 
253
  loss_fn = nn.BCEWithLogitsLoss()
254
 
255
+ print("\nTraining Baseline NN...")
256
+ for e in range(5):
257
+ opt1.zero_grad()
258
+ loss = loss_fn(baseline(Xt_t,Xa_t,Xv_t).squeeze(), yt)
259
+ loss.backward()
260
+ opt1.step()
261
+ print(f"Epoch {e+1}, Loss: {loss.item():.4f}")
262
 
263
+ print("\nTraining Attention Model...")
264
+ for e in range(5):
265
+ opt2.zero_grad()
266
+ loss = loss_fn(attention(Xt_t,Xa_t,Xv_t).squeeze(), yt)
267
  loss.backward()
268
+ opt2.step()
269
+ print(f"Epoch {e+1}, Loss: {loss.item():.4f}")
270
 
271
  # =========================
272
  # 12. EVALUATION
273
  # =========================
274
  with torch.no_grad():
275
+ pred_base = (torch.sigmoid(baseline(Xt_test_t,Xa_test_t,Xv_test_t).squeeze())>0.5).int().cpu().numpy()
276
+ pred_attn = (torch.sigmoid(attention(Xt_test_t,Xa_test_t,Xv_test_t).squeeze())>0.5).int().cpu().numpy()
 
277
 
278
  # =========================
279
  # 13. RESULTS
280
  # =========================
281
+ print("\n===== FINAL COMPARISON =====")
282
 
283
  print("\nRandom Forest:")
284
  print("Accuracy:", rf_acc)
285
  print("F1:", rf_f1)
286
 
287
+ print("\nBaseline NN:")
288
+ print("Accuracy:", accuracy_score(y_test, pred_base))
289
+ print("F1:", f1_score(y_test, pred_base))
290
+
291
+ print("\nAttention Model:")
292
+ print("Accuracy:", accuracy_score(y_test, pred_attn))
293
+ print("F1:", f1_score(y_test, pred_attn))