Yaning1001 commited on
Commit
69168b6
·
verified ·
1 Parent(s): 25a6535

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +4 -34
  2. .gitignore +1 -0
  3. FTP.py +316 -0
  4. FTP_update.py +323 -0
  5. FTP_update2.py +340 -0
  6. FTP_update3.py +329 -0
  7. FTP_update4.py +329 -0
  8. README.md +90 -0
  9. edge_probing/edge_probing.ipynb +0 -0
  10. edge_probing/edge_probing.py +222 -0
  11. edge_probing/edge_probing.sh +60 -0
  12. gpt2_no_positional_encoding_model.py +427 -0
  13. hop_interventions/create_agreement_data.py +85 -0
  14. hop_interventions/hop_interventions.ipynb +0 -0
  15. hop_interventions/hop_interventions.py +176 -0
  16. hop_surprisal/hop_surprisal.ipynb +223 -0
  17. hop_surprisal/hop_surprisal.py +193 -0
  18. hop_surprisal/hop_surprisal.sh +0 -0
  19. hop_surprisal/hop_surprisal_gpt2.py +204 -0
  20. hop_surprisal/hop_surprisal_llama3.py +205 -0
  21. hop_surprisal/hop_surprisal_results/GPT-2/hop_words4_10M/hop_words4_10M_seed0.csv +0 -0
  22. hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_control.csv +0 -0
  23. hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_control_10M_seed0.csv +0 -0
  24. hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_words4.csv +0 -0
  25. hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_words4_10M_seed0.csv +0 -0
  26. hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_control.csv +0 -0
  27. hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_control_10M_seed0.csv +0 -0
  28. hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_words4.csv +0 -0
  29. hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_words4_10M_seed0.csv +0 -0
  30. hop_surprisal/hop_surprisal_results/Qwen/3B/hop_control.csv +0 -0
  31. hop_surprisal/hop_surprisal_results/Qwen/3B/hop_words4.csv +0 -0
  32. hop_surprisal/hop_surprisal_results/Qwen/3B/hop_words4_10M_seed0.csv +0 -0
  33. hop_surprisal/hop_surprisal_results/Qwen/7B/hop_control.csv +0 -0
  34. hop_surprisal/hop_surprisal_results/Qwen/7B/hop_control_10M_seed0.csv +0 -0
  35. hop_surprisal/hop_surprisal_results/Qwen/7B/hop_words4.csv +0 -0
  36. hop_surprisal/hop_surprisal_results/Qwen/7B/hop_words4_10M_seed0.csv +0 -0
  37. impossible_llm.yaml +154 -0
  38. impossible_llm_update.yaml +162 -0
  39. perplexities/demo.py +82 -0
  40. perplexities/gpt2_mask.py +231 -0
  41. perplexities/gpt2_mask_test.py +0 -0
  42. perplexities/gpt2_noise.py +203 -0
  43. perplexities/gpt2_remove_layers.py +141 -0
  44. perplexities/gpt_mask.sh +9 -0
  45. perplexities/gpt_noise.sh +7 -0
  46. perplexities/llama3.2_remove_layers.py +135 -0
  47. perplexities/perplexities.py +179 -0
  48. perplexities/perplexities_llama.py +108 -0
  49. requirements.txt +94 -0
  50. requirements_1.txt +51 -0
.gitattributes CHANGED
@@ -1,35 +1,5 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ data/babylm_data/* filter=lfs diff=lfs merge=lfs -text
2
+ data/Perturbed_data/* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  *.pt filter=lfs diff=lfs merge=lfs -text
4
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
5
+ *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ train/checkpoints/
FTP.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.optim.optimizer import Optimizer, required
3
+ import copy
4
+ import math
5
+
6
+ class FTP(object):
7
+ def __init__(self, k=1.0, exclude_set={}):
8
+ self.exclude_set = exclude_set
9
+ self.threshold = torch.nn.Hardtanh(0,1)
10
+ self.k = k # Gradient annealing factor
11
+ self.j = 0 # Buffer counter
12
+
13
+ # AdamUtil parameteres
14
+ self.mu = 1e-2
15
+ self.beta1 = 0.9
16
+ self.beta2 = 0.999
17
+ self.t = 1
18
+
19
+ # Buffers
20
+ self.gamma_buff = []
21
+ self.first_m_gamma = []
22
+ self.second_m_gamma = []
23
+ self.prev_c = []
24
+ self.prev_scale = []
25
+
26
+ @torch.no_grad()
27
+ def step(self,name, curr, pre, d_p):
28
+ if curr.requires_grad and name not in self.exclude_set: # exclude_set includes those params that not be updated
29
+ c_t = (curr - d_p) - pre # (cur - d_p) potential new param/ dp gradient /
30
+ norms = self._mars_norm(c_t)
31
+
32
+ if self.t == 1:
33
+ gamma = torch.tensor(1e-8).to(norms.device)
34
+ self._update_buffers(gamma)
35
+ else:
36
+ # Get previous values
37
+ gamma_prev = self.gamma_buff[self.j]
38
+ c_prev = self.prev_c[self.j]
39
+ scale_prev = self.prev_scale[self.j]
40
+
41
+ # Calculate gradient for gamma
42
+ gamma_grad = torch.sum(self._dot(curr.grad, c_prev, scale = scale_prev))
43
+
44
+ # Anneal positive gradient
45
+ if gamma_grad > 0:
46
+ gamma_grad = gamma_grad * self.k
47
+
48
+ gamma = self._adam_util(gamma_prev, gamma_grad)
49
+
50
+ # Clip gamma
51
+ gamma = self._clip(gamma, norms)
52
+
53
+ # Update
54
+ denom = 1/norms
55
+ ratio = gamma * denom
56
+ new_p = pre + self.threshold(ratio) * c_t
57
+
58
+ # Save updated values
59
+ self._update_buffers(gamma, c_t, denom)
60
+ self.j += 1
61
+ return new_p
62
+ else:
63
+ return None
64
+
65
+ def incre_counters(self):
66
+ self.t += 1
67
+ self.j = 0
68
+
69
+ @torch.no_grad()
70
+ def _mars_norm(self,tensor):
71
+ return torch.sum(torch.abs(tensor), dim=tuple(range(1,tensor.dim())), keepdim=True) + 1e-8
72
+
73
+ @torch.no_grad()
74
+ def _clip(self, constraint, norms):
75
+ return torch.nn.functional.hardtanh(constraint,1e-8,norms.max())
76
+
77
+ @torch.no_grad()
78
+ def _dot(self,tensor1,tensor2,scale=1):
79
+ return torch.sum(torch.mul(tensor1, tensor2),dim=tuple(range(1,tensor1.dim())),keepdim=True)*scale
80
+
81
+ @torch.no_grad()
82
+ def _adam_util(self,prev, grad):
83
+ first_moment = self.beta1 * self.first_m_gamma[self.j] + (1-self.beta1) * grad
84
+ second_moment = self.beta2 * self.second_m_gamma[self.j] + (1-self.beta2) * grad**2
85
+ self.first_m_gamma[self.j] = first_moment
86
+ self.second_m_gamma[self.j] = second_moment
87
+ first_moment = first_moment/(1-self.beta1**self.t)
88
+ second_moment = second_moment/(1-self.beta2**self.t)
89
+ return prev - self.mu * first_moment/(torch.sqrt(second_moment)+1e-8)
90
+
91
+ def _update_buffers(self,gamma,c_t=None,denom=None):
92
+ if c_t is None:
93
+ self.first_m_gamma.append(0.0)
94
+ self.second_m_gamma.append(0.0)
95
+ self.gamma_buff.append(gamma)
96
+ self.prev_c.append(0.0)
97
+ self.prev_scale.append(0.0)
98
+ else:
99
+ self.gamma_buff[self.j] = gamma
100
+ self.prev_c[self.j] = c_t
101
+ self.prev_scale[self.j] = denom
102
+
103
+
104
+ class SGDP(Optimizer):
105
+ def __init__(self, params, lr=required, momentum=0, dampening=0,
106
+ weight_decay=0, nesterov=False, k=1.0, exclude_set = {}):
107
+ if lr is not required and lr < 0.0:
108
+ raise ValueError("Invalid learning rate: {}".format(lr))
109
+ if momentum < 0.0:
110
+ raise ValueError("Invalid momentum value: {}".format(momentum))
111
+ if weight_decay < 0.0:
112
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
113
+
114
+ defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
115
+ weight_decay=weight_decay, nesterov=nesterov)
116
+ if nesterov and (momentum <= 0 or dampening != 0):
117
+ raise ValueError("Nesterov momentum requires a momentum and zero dampening")
118
+ super(SGDP, self).__init__(params, defaults)
119
+ self.first_iter_flag = False
120
+
121
+ # initialize FTP
122
+ self.ftp = FTP(k, exclude_set=exclude_set)
123
+
124
+
125
+ def __setstate__(self, state):
126
+ super(SGDP, self).__setstate__(state)
127
+ for group in self.param_groups:
128
+ group.setdefault('nesterov', False)
129
+
130
+ @torch.no_grad()
131
+ def step(self, closure=None):
132
+ """Performs a single optimization step.
133
+
134
+ Arguments:
135
+ closure (callable, optional): A closure that reevaluates the model
136
+ and returns the loss.
137
+ """
138
+ loss = None
139
+ if closure is not None:
140
+ with torch.enable_grad():
141
+ loss = closure()
142
+ for group in self.param_groups:
143
+ weight_decay = group['weight_decay']
144
+ momentum = group['momentum']
145
+ dampening = group['dampening']
146
+ nesterov = group['nesterov']
147
+
148
+ for p, name, pre in zip(group['params'],group['name'],group['pre']):
149
+ if p.grad is None:
150
+ continue
151
+ d_p = p.grad
152
+ if weight_decay != 0:
153
+ d_p = d_p.add(p, alpha=weight_decay)
154
+ if momentum != 0:
155
+ param_state = self.state[p]
156
+ if 'momentum_buffer' not in param_state:
157
+ buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
158
+ else:
159
+ buf = param_state['momentum_buffer']
160
+ buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
161
+ if nesterov:
162
+ d_p = d_p.add(buf, alpha=momentum)
163
+ else:
164
+ d_p = buf
165
+
166
+ # FTP step
167
+ d_p = group['lr']*d_p
168
+ new_p = self.ftp.step(name,p,pre,d_p)
169
+ if new_p is not None :
170
+ p.copy_(new_p)
171
+ else:
172
+ p.add_(d_p, alpha=-1)
173
+ # FTP increment internal counters
174
+ self.ftp.incre_counters()
175
+ return loss
176
+
177
+
178
+ class AdamP(Optimizer):
179
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
180
+ weight_decay=0, amsgrad=False, k=1.0, exclude_set={}):
181
+ if not 0.0 <= lr:
182
+ raise ValueError("Invalid learning rate: {}".format(lr))
183
+ if not 0.0 <= eps:
184
+ raise ValueError("Invalid epsilon value: {}".format(eps))
185
+ if not 0.0 <= betas[0] < 1.0:
186
+ raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
187
+ if not 0.0 <= betas[1] < 1.0:
188
+ raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
189
+ if not 0.0 <= weight_decay:
190
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
191
+ defaults = dict(lr=lr, betas=betas, eps=eps,
192
+ weight_decay=weight_decay, amsgrad=amsgrad)
193
+ super(AdamP, self).__init__(params, defaults)
194
+
195
+ # initialize FTP
196
+ self.ftp = FTP(k, exclude_set=exclude_set)
197
+
198
+ def __setstate__(self, state):
199
+ super(AdamP, self).__setstate__(state)
200
+ for group in self.param_groups:
201
+ group.setdefault('amsgrad', False)
202
+
203
+ @torch.no_grad()
204
+ def step(self, closure=None):
205
+ """Performs a single optimization step.
206
+
207
+ Arguments:
208
+ closure (callable, optional): A closure that reevaluates the model
209
+ and returns the loss.
210
+ """
211
+ loss = None
212
+ if closure is not None:
213
+ with torch.enable_grad():
214
+ loss = closure()
215
+
216
+ for group in self.param_groups:
217
+ params_with_grad = []
218
+ grads = []
219
+ exp_avgs = []
220
+ exp_avg_sqs = []
221
+ max_exp_avg_sqs = []
222
+ state_steps = []
223
+
224
+ for p in group['params']:
225
+ if p.grad is not None:
226
+ params_with_grad.append(p)
227
+ if p.grad.is_sparse:
228
+ raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
229
+ grads.append(p.grad)
230
+
231
+ state = self.state[p]
232
+ # Lazy state initialization
233
+ if len(state) == 0:
234
+ state['step'] = 0
235
+ # Exponential moving average of gradient values
236
+ state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)
237
+ # Exponential moving average of squared gradient values
238
+ state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
239
+ if group['amsgrad']:
240
+ # Maintains max of all exp. moving avg. of sq. grad. values
241
+ state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
242
+
243
+ exp_avgs.append(state['exp_avg'])
244
+ exp_avg_sqs.append(state['exp_avg_sq'])
245
+
246
+ if group['amsgrad']:
247
+ max_exp_avg_sqs.append(state['max_exp_avg_sq'])
248
+
249
+ # update the steps for each param group update
250
+ state['step'] += 1
251
+ # record the step after step update
252
+ state_steps.append(state['step'])
253
+
254
+ beta1, beta2 = group['betas']
255
+ self.adam(group,
256
+ exp_avgs,
257
+ exp_avg_sqs,
258
+ max_exp_avg_sqs,
259
+ state_steps,
260
+ group['amsgrad'],
261
+ beta1,
262
+ beta2,
263
+ group['lr'],
264
+ group['weight_decay'],
265
+ group['eps']
266
+ )
267
+ # FTP increment internal counters
268
+ self.ftp.incre_counters()
269
+ return loss
270
+
271
+ def adam(self, group,
272
+ exp_avgs,
273
+ exp_avg_sqs,
274
+ max_exp_avg_sqs,
275
+ state_steps,
276
+ amsgrad: bool,
277
+ beta1: float,
278
+ beta2: float,
279
+ lr: float,
280
+ weight_decay: float,
281
+ eps: float):
282
+
283
+ i = 0
284
+ for param, name, pre in zip(group['params'],group['name'],group['pre']):
285
+ if param.grad is None:
286
+ continue
287
+ grad = param.grad
288
+ exp_avg = exp_avgs[i]
289
+ exp_avg_sq = exp_avg_sqs[i]
290
+ step = state_steps[i]
291
+ if amsgrad:
292
+ max_exp_avg_sq = max_exp_avg_sqs[i]
293
+
294
+ bias_correction1 = 1 - beta1 ** step
295
+ bias_correction2 = 1 - beta2 ** step
296
+
297
+ # Decay the first and second moment running average coefficient
298
+ exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
299
+ exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
300
+ if amsgrad:
301
+ # Maintains the maximum of all 2nd moment running avg. till now
302
+ torch.maximum(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
303
+ # Use the max. for normalizing running avg. of gradient
304
+ denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
305
+ else:
306
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
307
+
308
+ step_size = lr / bias_correction1
309
+ i += 1
310
+
311
+ # FTP step
312
+ d_p = step_size * exp_avg/denom + lr * weight_decay * param
313
+ new_p = self.ftp.step(name,param,pre,d_p)
314
+ if new_p is None :
315
+ new_p = param - d_p
316
+ param.copy_(new_p)
FTP_update.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.optim.optimizer import Optimizer, required
3
+ import copy
4
+ import math
5
+
6
+ class FTP(object):
7
+ def __init__(self, k=1.0, exclude_set={}):
8
+ self.exclude_set = exclude_set
9
+ self.threshold = torch.nn.Hardtanh(0,1)
10
+ self.k = k # Gradient annealing factor
11
+ self.j = 0 # Buffer counter
12
+
13
+ # AdamUtil parameters
14
+ self.mu = 1e-2
15
+ self.beta1 = 0.9
16
+ self.beta2 = 0.999
17
+ self.t = 1
18
+
19
+ # Buffers
20
+ self.gamma_buff = []
21
+ self.first_m_gamma = []
22
+ self.second_m_gamma = []
23
+ self.prev_c = []
24
+ self.prev_scale = []
25
+
26
+ @torch.no_grad()
27
+ def step(self, name, curr, pre, d_p):
28
+ if curr.requires_grad and name not in self.exclude_set: # Exclude set includes those params that not be updated
29
+ c_t = (curr - d_p) - pre # Compute potential new param
30
+ norms = self._mars_norm(c_t)
31
+
32
+ # New: Apply spectral normalization to c_t
33
+ c_t = self._apply_spectral_norm(c_t)
34
+
35
+ if self.t == 1:
36
+ gamma = torch.tensor(1e-8, device=norms.device)
37
+ self._update_buffers(gamma)
38
+ else:
39
+ # Get previous values
40
+ gamma_prev = self.gamma_buff[self.j]
41
+ c_prev = self.prev_c[self.j]
42
+ scale_prev = self.prev_scale[self.j]
43
+
44
+ # Calculate gradient for gamma
45
+ gamma_grad = torch.sum(self._dot(curr.grad, c_prev, scale=scale_prev))
46
+
47
+ # Anneal positive gradient
48
+ if gamma_grad > 0:
49
+ gamma_grad = gamma_grad * self.k
50
+
51
+ gamma = self._adam_util(gamma_prev, gamma_grad)
52
+
53
+ # Clip gamma
54
+ gamma = self._clip(gamma, norms)
55
+
56
+ # Update
57
+ denom = 1/norms
58
+ ratio = gamma * denom
59
+ new_p = pre + self.threshold(ratio) * c_t
60
+
61
+ # Save updated values
62
+ self._update_buffers(gamma, c_t, denom)
63
+ self.j += 1
64
+ return new_p
65
+ else:
66
+ return None
67
+
68
+ def _apply_spectral_norm(self, c_t):
69
+ u, s, vh = torch.linalg.svd(c_t, full_matrices=False)
70
+ spectral_norm = s.max()
71
+ return torch.matmul(u, vh) / spectral_norm
72
+
73
+ def incre_counters(self):
74
+ self.t += 1
75
+ self.j = 0
76
+
77
+ @torch.no_grad()
78
+ def _mars_norm(self, tensor):
79
+ return torch.sum(torch.abs(tensor), dim=tuple(range(1, tensor.dim())), keepdim=True) + 1e-8
80
+
81
+ @torch.no_grad()
82
+ def _clip(self, constraint, norms):
83
+ return torch.nn.functional.hardtanh(constraint, 1e-8, norms.max())
84
+
85
+ @torch.no_grad()
86
+ def _dot(self, tensor1, tensor2, scale=1):
87
+ return torch.sum(torch.mul(tensor1, tensor2), dim=tuple(range(1, tensor1.dim())), keepdim=True) * scale
88
+
89
+ @torch.no_grad()
90
+ def _adam_util(self, prev, grad):
91
+ first_moment = self.beta1 * self.first_m_gamma[self.j] + (1 - self.beta1) * grad
92
+ second_moment = self.beta2 * self.second_m_gamma[self.j] + (1 - self.beta2) * grad**2
93
+ self.first_m_gamma[self.j] = first_moment
94
+ self.second_m_gamma[self.j] = second_moment
95
+ first_moment = first_moment / (1 - self.beta1**self.t)
96
+ second_moment = second_moment / (1 - self.beta2**self.t)
97
+ return prev - self.mu * first_moment / (torch.sqrt(second_moment) + 1e-8)
98
+
99
+ def _update_buffers(self, gamma, c_t=None, denom=None):
100
+ if c_t is None:
101
+ self.first_m_gamma.append(0.0)
102
+ self.second_m_gamma.append(0.0)
103
+ self.gamma_buff.append(gamma)
104
+ self.prev_c.append(0.0)
105
+ self.prev_scale.append(0.0)
106
+ else:
107
+ self.gamma_buff[self.j] = gamma
108
+ self.prev_c[self.j] = c_t
109
+ self.prev_scale[self.j] = denom
110
+
111
+ class SGDP(Optimizer):
112
+ def __init__(self, params, lr=required, momentum=0, dampening=0,
113
+ weight_decay=0, nesterov=False, k=1.0, exclude_set = {}):
114
+ if lr is not required and lr < 0.0:
115
+ raise ValueError("Invalid learning rate: {}".format(lr))
116
+ if momentum < 0.0:
117
+ raise ValueError("Invalid momentum value: {}".format(momentum))
118
+ if weight_decay < 0.0:
119
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
120
+
121
+ defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
122
+ weight_decay=weight_decay, nesterov=nesterov)
123
+ if nesterov and (momentum <= 0 or dampening != 0):
124
+ raise ValueError("Nesterov momentum requires a momentum and zero dampening")
125
+ super(SGDP, self).__init__(params, defaults)
126
+ self.first_iter_flag = False
127
+
128
+ # initialize FTP
129
+ self.ftp = FTP(k, exclude_set=exclude_set)
130
+
131
+
132
+ def __setstate__(self, state):
133
+ super(SGDP, self).__setstate__(state)
134
+ for group in self.param_groups:
135
+ group.setdefault('nesterov', False)
136
+
137
+ @torch.no_grad()
138
+ def step(self, closure=None):
139
+ """Performs a single optimization step.
140
+
141
+ Arguments:
142
+ closure (callable, optional): A closure that reevaluates the model
143
+ and returns the loss.
144
+ """
145
+ loss = None
146
+ if closure is not None:
147
+ with torch.enable_grad():
148
+ loss = closure()
149
+ for group in self.param_groups:
150
+ weight_decay = group['weight_decay']
151
+ momentum = group['momentum']
152
+ dampening = group['dampening']
153
+ nesterov = group['nesterov']
154
+
155
+ for p, name, pre in zip(group['params'],group['name'],group['pre']):
156
+ if p.grad is None:
157
+ continue
158
+ d_p = p.grad
159
+ if weight_decay != 0:
160
+ d_p = d_p.add(p, alpha=weight_decay)
161
+ if momentum != 0:
162
+ param_state = self.state[p]
163
+ if 'momentum_buffer' not in param_state:
164
+ buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
165
+ else:
166
+ buf = param_state['momentum_buffer']
167
+ buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
168
+ if nesterov:
169
+ d_p = d_p.add(buf, alpha=momentum)
170
+ else:
171
+ d_p = buf
172
+
173
+ # FTP step
174
+ d_p = group['lr']*d_p
175
+ new_p = self.ftp.step(name,p,pre,d_p)
176
+ if new_p is not None :
177
+ p.copy_(new_p)
178
+ else:
179
+ p.add_(d_p, alpha=-1)
180
+ # FTP increment internal counters
181
+ self.ftp.incre_counters()
182
+ return loss
183
+
184
+
185
+ class AdamP(Optimizer):
186
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
187
+ weight_decay=0, amsgrad=False, k=1.0, exclude_set={}):
188
+ if not 0.0 <= lr:
189
+ raise ValueError("Invalid learning rate: {}".format(lr))
190
+ if not 0.0 <= eps:
191
+ raise ValueError("Invalid epsilon value: {}".format(eps))
192
+ if not 0.0 <= betas[0] < 1.0:
193
+ raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
194
+ if not 0.0 <= betas[1] < 1.0:
195
+ raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
196
+ if not 0.0 <= weight_decay:
197
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
198
+ defaults = dict(lr=lr, betas=betas, eps=eps,
199
+ weight_decay=weight_decay, amsgrad=amsgrad)
200
+ super(AdamP, self).__init__(params, defaults)
201
+
202
+ # initialize FTP
203
+ self.ftp = FTP(k, exclude_set=exclude_set)
204
+
205
+ def __setstate__(self, state):
206
+ super(AdamP, self).__setstate__(state)
207
+ for group in self.param_groups:
208
+ group.setdefault('amsgrad', False)
209
+
210
+ @torch.no_grad()
211
+ def step(self, closure=None):
212
+ """Performs a single optimization step.
213
+
214
+ Arguments:
215
+ closure (callable, optional): A closure that reevaluates the model
216
+ and returns the loss.
217
+ """
218
+ loss = None
219
+ if closure is not None:
220
+ with torch.enable_grad():
221
+ loss = closure()
222
+
223
+ for group in self.param_groups:
224
+ params_with_grad = []
225
+ grads = []
226
+ exp_avgs = []
227
+ exp_avg_sqs = []
228
+ max_exp_avg_sqs = []
229
+ state_steps = []
230
+
231
+ for p in group['params']:
232
+ if p.grad is not None:
233
+ params_with_grad.append(p)
234
+ if p.grad.is_sparse:
235
+ raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
236
+ grads.append(p.grad)
237
+
238
+ state = self.state[p]
239
+ # Lazy state initialization
240
+ if len(state) == 0:
241
+ state['step'] = 0
242
+ # Exponential moving average of gradient values
243
+ state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)
244
+ # Exponential moving average of squared gradient values
245
+ state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
246
+ if group['amsgrad']:
247
+ # Maintains max of all exp. moving avg. of sq. grad. values
248
+ state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
249
+
250
+ exp_avgs.append(state['exp_avg'])
251
+ exp_avg_sqs.append(state['exp_avg_sq'])
252
+
253
+ if group['amsgrad']:
254
+ max_exp_avg_sqs.append(state['max_exp_avg_sq'])
255
+
256
+ # update the steps for each param group update
257
+ state['step'] += 1
258
+ # record the step after step update
259
+ state_steps.append(state['step'])
260
+
261
+ beta1, beta2 = group['betas']
262
+ self.adam(group,
263
+ exp_avgs,
264
+ exp_avg_sqs,
265
+ max_exp_avg_sqs,
266
+ state_steps,
267
+ group['amsgrad'],
268
+ beta1,
269
+ beta2,
270
+ group['lr'],
271
+ group['weight_decay'],
272
+ group['eps']
273
+ )
274
+ # FTP increment internal counters
275
+ self.ftp.incre_counters()
276
+ return loss
277
+
278
+ def adam(self, group,
279
+ exp_avgs,
280
+ exp_avg_sqs,
281
+ max_exp_avg_sqs,
282
+ state_steps,
283
+ amsgrad: bool,
284
+ beta1: float,
285
+ beta2: float,
286
+ lr: float,
287
+ weight_decay: float,
288
+ eps: float):
289
+
290
+ i = 0
291
+ for param, name, pre in zip(group['params'],group['name'],group['pre']):
292
+ if param.grad is None:
293
+ continue
294
+ grad = param.grad
295
+ exp_avg = exp_avgs[i]
296
+ exp_avg_sq = exp_avg_sqs[i]
297
+ step = state_steps[i]
298
+ if amsgrad:
299
+ max_exp_avg_sq = max_exp_avg_sqs[i]
300
+
301
+ bias_correction1 = 1 - beta1 ** step
302
+ bias_correction2 = 1 - beta2 ** step
303
+
304
+ # Decay the first and second moment running average coefficient
305
+ exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
306
+ exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
307
+ if amsgrad:
308
+ # Maintains the maximum of all 2nd moment running avg. till now
309
+ torch.maximum(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
310
+ # Use the max. for normalizing running avg. of gradient
311
+ denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
312
+ else:
313
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
314
+
315
+ step_size = lr / bias_correction1
316
+ i += 1
317
+
318
+ # FTP step
319
+ d_p = step_size * exp_avg/denom + lr * weight_decay * param
320
+ new_p = self.ftp.step(name,param,pre,d_p)
321
+ if new_p is None :
322
+ new_p = param - d_p
323
+ param.copy_(new_p)
FTP_update2.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.optim.optimizer import Optimizer, required
3
+ import copy
4
+ import math
5
+
6
+ class FTP(object):
7
+ def __init__(self, k=1.0, exclude_set={}):
8
+ self.exclude_set = exclude_set
9
+ self.threshold = torch.nn.Hardtanh(0,1)
10
+ self.k = k # Gradient annealing factor
11
+ self.j = 0 # Buffer counter
12
+
13
+ # AdamUtil parameters
14
+ self.mu = 1e-2
15
+ self.beta1 = 0.9
16
+ self.beta2 = 0.999
17
+ self.t = 1
18
+
19
+ # Buffers
20
+ self.gamma_buff = []
21
+ self.first_m_gamma = []
22
+ self.second_m_gamma = []
23
+ self.prev_c = []
24
+ self.prev_scale = []
25
+
26
+ @torch.no_grad()
27
+ def step(self, name, curr, pre, d_p):
28
+ if curr.requires_grad and name not in self.exclude_set: # Exclude set includes those params that not be updated
29
+ c_t = (curr - d_p) - pre # Compute potential new param
30
+ norms = self._mars_norm(c_t)
31
+
32
+ # New: Apply spectral normalization to c_t
33
+ c_t = self._apply_spectral_norm(c_t)
34
+
35
+ if self.t == 1:
36
+ gamma = torch.tensor(1e-8, device=norms.device)
37
+ self._update_buffers(gamma)
38
+ else:
39
+ # Get previous values
40
+ gamma_prev = self.gamma_buff[self.j]
41
+ c_prev = self.prev_c[self.j]
42
+ scale_prev = self.prev_scale[self.j]
43
+
44
+ # Calculate gradient for gamma
45
+ gamma_grad = torch.sum(self._dot(curr.grad, c_prev, scale=scale_prev))
46
+
47
+ # Anneal positive gradient
48
+ if gamma_grad > 0:
49
+ gamma_grad = gamma_grad * self.k
50
+
51
+ gamma = self._adam_util(gamma_prev, gamma_grad)
52
+
53
+ # Clip gamma
54
+ gamma = self._clip(gamma, norms)
55
+
56
+ # Update
57
+ denom = 1/norms
58
+ ratio = gamma * denom
59
+ new_p = pre + self.threshold(ratio) * c_t
60
+
61
+ # Save updated values
62
+ self._update_buffers(gamma, c_t, denom)
63
+ self.j += 1
64
+ return new_p
65
+ else:
66
+ return None
67
+
68
+ def _apply_spectral_norm(self, param, tau=1.0):
69
+
70
+ if param.ndim >= 2: # Only for 2D matrices
71
+ u, s, vh = torch.linalg.svd(param, full_matrices=False)
72
+ s_clipped = torch.clamp(s, max=tau) # Clip singular values
73
+ return torch.matmul(u, torch.matmul(torch.diag(s_clipped), vh))
74
+ return param # No projection for 1D parameters
75
+
76
+ # def _apply_spectral_norm(self, param, tau=1.0):
77
+ # if param.ndim >= 2: # For 2D or higher-dimensional tensors
78
+ # original_shape = param.shape # Save original shape
79
+ # reshaped_param = param.view(original_shape[0], -1) # Flatten into 2D
80
+
81
+ # u, s, vh = torch.linalg.svd(reshaped_param, full_matrices=False)
82
+ # s_clipped = torch.clamp(s, max=tau) # Clip singular values
83
+
84
+ # # Reconstruct the tensor with clipped singular values
85
+ # normalized_param = torch.matmul(u, torch.matmul(torch.diag(s_clipped), vh))
86
+ # return normalized_param.view(original_shape) # Reshape back to original
87
+ # return param # No projection for 1D parameters
88
+
89
+
90
+ def incre_counters(self):
91
+ self.t += 1
92
+ self.j = 0
93
+
94
+ @torch.no_grad()
95
+ def _mars_norm(self, tensor):
96
+ return torch.sum(torch.abs(tensor), dim=tuple(range(1, tensor.dim())), keepdim=True) + 1e-8
97
+
98
+ @torch.no_grad()
99
+ def _clip(self, constraint, norms):
100
+ return torch.nn.functional.hardtanh(constraint, 1e-8, norms.max())
101
+
102
+ @torch.no_grad()
103
+ def _dot(self, tensor1, tensor2, scale=1):
104
+ return torch.sum(torch.mul(tensor1, tensor2), dim=tuple(range(1, tensor1.dim())), keepdim=True) * scale
105
+
106
+ @torch.no_grad()
107
+ def _adam_util(self, prev, grad):
108
+ first_moment = self.beta1 * self.first_m_gamma[self.j] + (1 - self.beta1) * grad
109
+ second_moment = self.beta2 * self.second_m_gamma[self.j] + (1 - self.beta2) * grad**2
110
+ self.first_m_gamma[self.j] = first_moment
111
+ self.second_m_gamma[self.j] = second_moment
112
+ first_moment = first_moment / (1 - self.beta1**self.t)
113
+ second_moment = second_moment / (1 - self.beta2**self.t)
114
+ return prev - self.mu * first_moment / (torch.sqrt(second_moment) + 1e-8)
115
+
116
+ def _update_buffers(self, gamma, c_t=None, denom=None):
117
+ if c_t is None:
118
+ self.first_m_gamma.append(0.0)
119
+ self.second_m_gamma.append(0.0)
120
+ self.gamma_buff.append(gamma)
121
+ self.prev_c.append(0.0)
122
+ self.prev_scale.append(0.0)
123
+ else:
124
+ self.gamma_buff[self.j] = gamma
125
+ self.prev_c[self.j] = c_t
126
+ self.prev_scale[self.j] = denom
127
+
128
+ class SGDP(Optimizer):
129
+ def __init__(self, params, lr=required, momentum=0, dampening=0,
130
+ weight_decay=0, nesterov=False, k=1.0, exclude_set = {}):
131
+ if lr is not required and lr < 0.0:
132
+ raise ValueError("Invalid learning rate: {}".format(lr))
133
+ if momentum < 0.0:
134
+ raise ValueError("Invalid momentum value: {}".format(momentum))
135
+ if weight_decay < 0.0:
136
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
137
+
138
+ defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
139
+ weight_decay=weight_decay, nesterov=nesterov)
140
+ if nesterov and (momentum <= 0 or dampening != 0):
141
+ raise ValueError("Nesterov momentum requires a momentum and zero dampening")
142
+ super(SGDP, self).__init__(params, defaults)
143
+ self.first_iter_flag = False
144
+
145
+ # initialize FTP
146
+ self.ftp = FTP(k, exclude_set=exclude_set)
147
+
148
+
149
+ def __setstate__(self, state):
150
+ super(SGDP, self).__setstate__(state)
151
+ for group in self.param_groups:
152
+ group.setdefault('nesterov', False)
153
+
154
+ @torch.no_grad()
155
+ def step(self, closure=None):
156
+ """Performs a single optimization step.
157
+
158
+ Arguments:
159
+ closure (callable, optional): A closure that reevaluates the model
160
+ and returns the loss.
161
+ """
162
+ loss = None
163
+ if closure is not None:
164
+ with torch.enable_grad():
165
+ loss = closure()
166
+ for group in self.param_groups:
167
+ weight_decay = group['weight_decay']
168
+ momentum = group['momentum']
169
+ dampening = group['dampening']
170
+ nesterov = group['nesterov']
171
+
172
+ for p, name, pre in zip(group['params'],group['name'],group['pre']):
173
+ if p.grad is None:
174
+ continue
175
+ d_p = p.grad
176
+ if weight_decay != 0:
177
+ d_p = d_p.add(p, alpha=weight_decay)
178
+ if momentum != 0:
179
+ param_state = self.state[p]
180
+ if 'momentum_buffer' not in param_state:
181
+ buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
182
+ else:
183
+ buf = param_state['momentum_buffer']
184
+ buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
185
+ if nesterov:
186
+ d_p = d_p.add(buf, alpha=momentum)
187
+ else:
188
+ d_p = buf
189
+
190
+ # FTP step
191
+ d_p = group['lr']*d_p
192
+ new_p = self.ftp.step(name,p,pre,d_p)
193
+ if new_p is not None :
194
+ p.copy_(new_p)
195
+ else:
196
+ p.add_(d_p, alpha=-1)
197
+ # FTP increment internal counters
198
+ self.ftp.incre_counters()
199
+ return loss
200
+
201
+
202
+ class AdamP(Optimizer):
203
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
204
+ weight_decay=0, amsgrad=False, k=1.0, exclude_set={}):
205
+ if not 0.0 <= lr:
206
+ raise ValueError("Invalid learning rate: {}".format(lr))
207
+ if not 0.0 <= eps:
208
+ raise ValueError("Invalid epsilon value: {}".format(eps))
209
+ if not 0.0 <= betas[0] < 1.0:
210
+ raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
211
+ if not 0.0 <= betas[1] < 1.0:
212
+ raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
213
+ if not 0.0 <= weight_decay:
214
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
215
+ defaults = dict(lr=lr, betas=betas, eps=eps,
216
+ weight_decay=weight_decay, amsgrad=amsgrad)
217
+ super(AdamP, self).__init__(params, defaults)
218
+
219
+ # initialize FTP
220
+ self.ftp = FTP(k, exclude_set=exclude_set)
221
+
222
+ def __setstate__(self, state):
223
+ super(AdamP, self).__setstate__(state)
224
+ for group in self.param_groups:
225
+ group.setdefault('amsgrad', False)
226
+
227
+ @torch.no_grad()
228
+ def step(self, closure=None):
229
+ """Performs a single optimization step.
230
+
231
+ Arguments:
232
+ closure (callable, optional): A closure that reevaluates the model
233
+ and returns the loss.
234
+ """
235
+ loss = None
236
+ if closure is not None:
237
+ with torch.enable_grad():
238
+ loss = closure()
239
+
240
+ for group in self.param_groups:
241
+ params_with_grad = []
242
+ grads = []
243
+ exp_avgs = []
244
+ exp_avg_sqs = []
245
+ max_exp_avg_sqs = []
246
+ state_steps = []
247
+
248
+ for p in group['params']:
249
+ if p.grad is not None:
250
+ params_with_grad.append(p)
251
+ if p.grad.is_sparse:
252
+ raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
253
+ grads.append(p.grad)
254
+
255
+ state = self.state[p]
256
+ # Lazy state initialization
257
+ if len(state) == 0:
258
+ state['step'] = 0
259
+ # Exponential moving average of gradient values
260
+ state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)
261
+ # Exponential moving average of squared gradient values
262
+ state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
263
+ if group['amsgrad']:
264
+ # Maintains max of all exp. moving avg. of sq. grad. values
265
+ state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
266
+
267
+ exp_avgs.append(state['exp_avg'])
268
+ exp_avg_sqs.append(state['exp_avg_sq'])
269
+
270
+ if group['amsgrad']:
271
+ max_exp_avg_sqs.append(state['max_exp_avg_sq'])
272
+
273
+ # update the steps for each param group update
274
+ state['step'] += 1
275
+ # record the step after step update
276
+ state_steps.append(state['step'])
277
+
278
+ beta1, beta2 = group['betas']
279
+ self.adam(group,
280
+ exp_avgs,
281
+ exp_avg_sqs,
282
+ max_exp_avg_sqs,
283
+ state_steps,
284
+ group['amsgrad'],
285
+ beta1,
286
+ beta2,
287
+ group['lr'],
288
+ group['weight_decay'],
289
+ group['eps']
290
+ )
291
+ # FTP increment internal counters
292
+ self.ftp.incre_counters()
293
+ return loss
294
+
295
+ def adam(self, group,
296
+ exp_avgs,
297
+ exp_avg_sqs,
298
+ max_exp_avg_sqs,
299
+ state_steps,
300
+ amsgrad: bool,
301
+ beta1: float,
302
+ beta2: float,
303
+ lr: float,
304
+ weight_decay: float,
305
+ eps: float):
306
+
307
+ i = 0
308
+ for param, name, pre in zip(group['params'],group['name'],group['pre']):
309
+ if param.grad is None:
310
+ continue
311
+ grad = param.grad
312
+ exp_avg = exp_avgs[i]
313
+ exp_avg_sq = exp_avg_sqs[i]
314
+ step = state_steps[i]
315
+ if amsgrad:
316
+ max_exp_avg_sq = max_exp_avg_sqs[i]
317
+
318
+ bias_correction1 = 1 - beta1 ** step
319
+ bias_correction2 = 1 - beta2 ** step
320
+
321
+ # Decay the first and second moment running average coefficient
322
+ exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
323
+ exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
324
+ if amsgrad:
325
+ # Maintains the maximum of all 2nd moment running avg. till now
326
+ torch.maximum(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
327
+ # Use the max. for normalizing running avg. of gradient
328
+ denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
329
+ else:
330
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
331
+
332
+ step_size = lr / bias_correction1
333
+ i += 1
334
+
335
+ # FTP step
336
+ d_p = step_size * exp_avg/denom + lr * weight_decay * param
337
+ new_p = self.ftp.step(name,param,pre,d_p)
338
+ if new_p is None :
339
+ new_p = param - d_p
340
+ param.copy_(new_p)
FTP_update3.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.optim.optimizer import Optimizer, required
3
+ import copy
4
+ import math
5
+
6
+ class FTP(object):
7
+ def __init__(self, k=1.0, exclude_set={}):
8
+ self.exclude_set = exclude_set
9
+ self.threshold = torch.nn.Hardtanh(0,1)
10
+ self.k = k # Gradient annealing factor
11
+ self.j = 0 # Buffer counter
12
+
13
+ # AdamUtil parameters
14
+ self.mu = 1e-2
15
+ self.beta1 = 0.9
16
+ self.beta2 = 0.999
17
+ self.t = 1
18
+
19
+ # Buffers
20
+ self.gamma_buff = []
21
+ self.first_m_gamma = []
22
+ self.second_m_gamma = []
23
+ self.prev_c = []
24
+ self.prev_scale = []
25
+
26
+ @torch.no_grad()
27
+ def step(self, name, curr, pre, d_p):
28
+ if curr.requires_grad and name not in self.exclude_set: # Exclude set includes those params that not be updated
29
+ c_t = (curr - d_p) - pre # Compute potential new param
30
+ norms = self._mars_norm(c_t, tau=1.0)
31
+
32
+ # New: Apply spectral normalization to c_t
33
+ c_t = self._apply_spectral_norm(c_t)
34
+
35
+ if self.t == 1:
36
+ gamma = torch.tensor(1e-8, device=norms.device)
37
+ self._update_buffers(gamma)
38
+ else:
39
+ # Get previous values
40
+ gamma_prev = self.gamma_buff[self.j]
41
+ c_prev = self.prev_c[self.j]
42
+ scale_prev = self.prev_scale[self.j]
43
+
44
+ # Calculate gradient for gamma
45
+ gamma_grad = torch.sum(self._dot(curr.grad, c_prev, scale=scale_prev))
46
+
47
+ # Anneal positive gradient
48
+ if gamma_grad > 0:
49
+ gamma_grad = gamma_grad * self.k
50
+
51
+ gamma = self._adam_util(gamma_prev, gamma_grad)
52
+
53
+ # Clip gamma
54
+ gamma = self._clip(gamma, norms)
55
+
56
+ # Update
57
+ denom = 1/norms
58
+ ratio = gamma * denom
59
+ new_p = pre + self.threshold(ratio) * c_t
60
+
61
+ new_p = self._apply_spectral_norm(new_p, tau=2.0) # tau is the max allowed spectral norm
62
+
63
+ # Save updated values
64
+ self._update_buffers(gamma, c_t, denom)
65
+ self.j += 1
66
+ return new_p
67
+ else:
68
+ return None
69
+
70
+ def _apply_spectral_norm(self, param, tau=1.0):
71
+
72
+ if param.ndim >= 2: # Only for 2D matrices
73
+ u, s, vh = torch.linalg.svd(param, full_matrices=False)
74
+ s_clipped = torch.clamp(s, max=tau) # Clip singular values
75
+ return torch.matmul(u, torch.matmul(torch.diag(s_clipped), vh))
76
+ return param # No projection for 1D parameters
77
+
78
+
79
+ def incre_counters(self):
80
+ self.t += 1
81
+ self.j = 0
82
+
83
+ @torch.no_grad()
84
+ def _mars_norm(self, tensor):
85
+ return torch.sum(torch.abs(tensor), dim=tuple(range(1, tensor.dim())), keepdim=True) + 1e-8
86
+
87
+ @torch.no_grad()
88
+ def _clip(self, constraint, norms):
89
+ return torch.nn.functional.hardtanh(constraint, 1e-8, norms.max())
90
+
91
+ @torch.no_grad()
92
+ def _dot(self, tensor1, tensor2, scale=1):
93
+ return torch.sum(torch.mul(tensor1, tensor2), dim=tuple(range(1, tensor1.dim())), keepdim=True) * scale
94
+
95
+ @torch.no_grad()
96
+ def _adam_util(self, prev, grad):
97
+ first_moment = self.beta1 * self.first_m_gamma[self.j] + (1 - self.beta1) * grad
98
+ second_moment = self.beta2 * self.second_m_gamma[self.j] + (1 - self.beta2) * grad**2
99
+ self.first_m_gamma[self.j] = first_moment
100
+ self.second_m_gamma[self.j] = second_moment
101
+ first_moment = first_moment / (1 - self.beta1**self.t)
102
+ second_moment = second_moment / (1 - self.beta2**self.t)
103
+ return prev - self.mu * first_moment / (torch.sqrt(second_moment) + 1e-8)
104
+
105
+ def _update_buffers(self, gamma, c_t=None, denom=None):
106
+ if c_t is None:
107
+ self.first_m_gamma.append(0.0)
108
+ self.second_m_gamma.append(0.0)
109
+ self.gamma_buff.append(gamma)
110
+ self.prev_c.append(0.0)
111
+ self.prev_scale.append(0.0)
112
+ else:
113
+ self.gamma_buff[self.j] = gamma
114
+ self.prev_c[self.j] = c_t
115
+ self.prev_scale[self.j] = denom
116
+
117
+ class SGDP(Optimizer):
118
+ def __init__(self, params, lr=required, momentum=0, dampening=0,
119
+ weight_decay=0, nesterov=False, k=1.0, exclude_set = {}):
120
+ if lr is not required and lr < 0.0:
121
+ raise ValueError("Invalid learning rate: {}".format(lr))
122
+ if momentum < 0.0:
123
+ raise ValueError("Invalid momentum value: {}".format(momentum))
124
+ if weight_decay < 0.0:
125
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
126
+
127
+ defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
128
+ weight_decay=weight_decay, nesterov=nesterov)
129
+ if nesterov and (momentum <= 0 or dampening != 0):
130
+ raise ValueError("Nesterov momentum requires a momentum and zero dampening")
131
+ super(SGDP, self).__init__(params, defaults)
132
+ self.first_iter_flag = False
133
+
134
+ # initialize FTP
135
+ self.ftp = FTP(k, exclude_set=exclude_set)
136
+
137
+
138
+ def __setstate__(self, state):
139
+ super(SGDP, self).__setstate__(state)
140
+ for group in self.param_groups:
141
+ group.setdefault('nesterov', False)
142
+
143
+ @torch.no_grad()
144
+ def step(self, closure=None):
145
+ """Performs a single optimization step.
146
+
147
+ Arguments:
148
+ closure (callable, optional): A closure that reevaluates the model
149
+ and returns the loss.
150
+ """
151
+ loss = None
152
+ if closure is not None:
153
+ with torch.enable_grad():
154
+ loss = closure()
155
+ for group in self.param_groups:
156
+ weight_decay = group['weight_decay']
157
+ momentum = group['momentum']
158
+ dampening = group['dampening']
159
+ nesterov = group['nesterov']
160
+
161
+ for p, name, pre in zip(group['params'],group['name'],group['pre']):
162
+ if p.grad is None:
163
+ continue
164
+ d_p = p.grad
165
+ if weight_decay != 0:
166
+ d_p = d_p.add(p, alpha=weight_decay)
167
+ if momentum != 0:
168
+ param_state = self.state[p]
169
+ if 'momentum_buffer' not in param_state:
170
+ buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
171
+ else:
172
+ buf = param_state['momentum_buffer']
173
+ buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
174
+ if nesterov:
175
+ d_p = d_p.add(buf, alpha=momentum)
176
+ else:
177
+ d_p = buf
178
+
179
+ # FTP step
180
+ d_p = group['lr']*d_p
181
+ new_p = self.ftp.step(name,p,pre,d_p)
182
+ if new_p is not None :
183
+ p.copy_(new_p)
184
+ else:
185
+ p.add_(d_p, alpha=-1)
186
+ # FTP increment internal counters
187
+ self.ftp.incre_counters()
188
+ return loss
189
+
190
+
191
+ class AdamP(Optimizer):
192
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
193
+ weight_decay=0, amsgrad=False, k=1.0, exclude_set={}):
194
+ if not 0.0 <= lr:
195
+ raise ValueError("Invalid learning rate: {}".format(lr))
196
+ if not 0.0 <= eps:
197
+ raise ValueError("Invalid epsilon value: {}".format(eps))
198
+ if not 0.0 <= betas[0] < 1.0:
199
+ raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
200
+ if not 0.0 <= betas[1] < 1.0:
201
+ raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
202
+ if not 0.0 <= weight_decay:
203
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
204
+ defaults = dict(lr=lr, betas=betas, eps=eps,
205
+ weight_decay=weight_decay, amsgrad=amsgrad)
206
+ super(AdamP, self).__init__(params, defaults)
207
+
208
+ # initialize FTP
209
+ self.ftp = FTP(k, exclude_set=exclude_set)
210
+
211
+ def __setstate__(self, state):
212
+ super(AdamP, self).__setstate__(state)
213
+ for group in self.param_groups:
214
+ group.setdefault('amsgrad', False)
215
+
216
+ @torch.no_grad()
217
+ def step(self, closure=None):
218
+ """Performs a single optimization step.
219
+
220
+ Arguments:
221
+ closure (callable, optional): A closure that reevaluates the model
222
+ and returns the loss.
223
+ """
224
+ loss = None
225
+ if closure is not None:
226
+ with torch.enable_grad():
227
+ loss = closure()
228
+
229
+ for group in self.param_groups:
230
+ params_with_grad = []
231
+ grads = []
232
+ exp_avgs = []
233
+ exp_avg_sqs = []
234
+ max_exp_avg_sqs = []
235
+ state_steps = []
236
+
237
+ for p in group['params']:
238
+ if p.grad is not None:
239
+ params_with_grad.append(p)
240
+ if p.grad.is_sparse:
241
+ raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
242
+ grads.append(p.grad)
243
+
244
+ state = self.state[p]
245
+ # Lazy state initialization
246
+ if len(state) == 0:
247
+ state['step'] = 0
248
+ # Exponential moving average of gradient values
249
+ state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)
250
+ # Exponential moving average of squared gradient values
251
+ state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
252
+ if group['amsgrad']:
253
+ # Maintains max of all exp. moving avg. of sq. grad. values
254
+ state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
255
+
256
+ exp_avgs.append(state['exp_avg'])
257
+ exp_avg_sqs.append(state['exp_avg_sq'])
258
+
259
+ if group['amsgrad']:
260
+ max_exp_avg_sqs.append(state['max_exp_avg_sq'])
261
+
262
+ # update the steps for each param group update
263
+ state['step'] += 1
264
+ # record the step after step update
265
+ state_steps.append(state['step'])
266
+
267
+ beta1, beta2 = group['betas']
268
+ self.adam(group,
269
+ exp_avgs,
270
+ exp_avg_sqs,
271
+ max_exp_avg_sqs,
272
+ state_steps,
273
+ group['amsgrad'],
274
+ beta1,
275
+ beta2,
276
+ group['lr'],
277
+ group['weight_decay'],
278
+ group['eps']
279
+ )
280
+ # FTP increment internal counters
281
+ self.ftp.incre_counters()
282
+ return loss
283
+
284
+ def adam(self, group,
285
+ exp_avgs,
286
+ exp_avg_sqs,
287
+ max_exp_avg_sqs,
288
+ state_steps,
289
+ amsgrad: bool,
290
+ beta1: float,
291
+ beta2: float,
292
+ lr: float,
293
+ weight_decay: float,
294
+ eps: float):
295
+
296
+ i = 0
297
+ for param, name, pre in zip(group['params'],group['name'],group['pre']):
298
+ if param.grad is None:
299
+ continue
300
+ grad = param.grad
301
+ exp_avg = exp_avgs[i]
302
+ exp_avg_sq = exp_avg_sqs[i]
303
+ step = state_steps[i]
304
+ if amsgrad:
305
+ max_exp_avg_sq = max_exp_avg_sqs[i]
306
+
307
+ bias_correction1 = 1 - beta1 ** step
308
+ bias_correction2 = 1 - beta2 ** step
309
+
310
+ # Decay the first and second moment running average coefficient
311
+ exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
312
+ exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
313
+ if amsgrad:
314
+ # Maintains the maximum of all 2nd moment running avg. till now
315
+ torch.maximum(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
316
+ # Use the max. for normalizing running avg. of gradient
317
+ denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
318
+ else:
319
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
320
+
321
+ step_size = lr / bias_correction1
322
+ i += 1
323
+
324
+ # FTP step
325
+ d_p = step_size * exp_avg/denom + lr * weight_decay * param
326
+ new_p = self.ftp.step(name,param,pre,d_p)
327
+ if new_p is None :
328
+ new_p = param - d_p
329
+ param.copy_(new_p)
FTP_update4.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.optim.optimizer import Optimizer, required
3
+ import copy
4
+ import math
5
+
6
+ class FTP(object):
7
+ def __init__(self, k=1.0, exclude_set={}):
8
+ self.exclude_set = exclude_set
9
+ self.threshold = torch.nn.Hardtanh(0,1)
10
+ self.k = k # Gradient annealing factor
11
+ self.j = 0 # Buffer counter
12
+
13
+ # AdamUtil parameters
14
+ self.mu = 1e-2
15
+ self.beta1 = 0.9
16
+ self.beta2 = 0.999
17
+ self.t = 1
18
+
19
+ # Buffers
20
+ self.gamma_buff = []
21
+ self.first_m_gamma = []
22
+ self.second_m_gamma = []
23
+ self.prev_c = []
24
+ self.prev_scale = []
25
+
26
+ @torch.no_grad()
27
+ def step(self, name, curr, pre, d_p):
28
+ if curr.requires_grad and name not in self.exclude_set: # Exclude set includes those params that not be updated
29
+ c_t = (curr - d_p) - pre # Compute potential new param
30
+ norms = self._mars_norm(c_t, tau=1.0)
31
+
32
+ # New: Apply spectral normalization to c_t
33
+ c_t = self._apply_spectral_norm(c_t)
34
+
35
+ if self.t == 1:
36
+ gamma = torch.tensor(1e-8, device=norms.device)
37
+ self._update_buffers(gamma)
38
+ else:
39
+ # Get previous values
40
+ gamma_prev = self.gamma_buff[self.j]
41
+ c_prev = self.prev_c[self.j]
42
+ scale_prev = self.prev_scale[self.j]
43
+
44
+ # Calculate gradient for gamma
45
+ gamma_grad = torch.sum(self._dot(curr.grad, c_prev, scale=scale_prev))
46
+
47
+ # Anneal positive gradient
48
+ if gamma_grad > 0:
49
+ gamma_grad = gamma_grad * self.k
50
+
51
+ gamma = self._adam_util(gamma_prev, gamma_grad)
52
+
53
+ # Clip gamma
54
+ gamma = self._clip(gamma, norms)
55
+
56
+ # Update
57
+ denom = 1/norms
58
+ ratio = gamma * denom
59
+ new_p = pre + self.threshold(ratio) * c_t
60
+
61
+ new_p = self._apply_spectral_norm(new_p, tau=2.0) # tau is the max allowed spectral norm
62
+
63
+ # Save updated values
64
+ self._update_buffers(gamma, c_t, denom)
65
+ self.j += 1
66
+ return new_p
67
+ else:
68
+ return None
69
+
70
+ def _apply_spectral_norm(self, param, tau=1.0):
71
+
72
+ if param.ndim >= 2: # Only for 2D matrices
73
+ u, s, vh = torch.linalg.svd(param, full_matrices=False)
74
+ s_clipped = torch.clamp(s, max=tau) # Clip singular values
75
+ return torch.matmul(u, torch.matmul(torch.diag(s_clipped), vh))
76
+ return param # No projection for 1D parameters
77
+
78
+
79
+ def incre_counters(self):
80
+ self.t += 1
81
+ self.j = 0
82
+
83
+ @torch.no_grad()
84
+ def _mars_norm(self, tensor):
85
+ return torch.sum(torch.abs(tensor), dim=tuple(range(1, tensor.dim())), keepdim=True) + 1e-8
86
+
87
+ @torch.no_grad()
88
+ def _clip(self, constraint, norms):
89
+ return torch.nn.functional.hardtanh(constraint, 1e-8, norms.max())
90
+
91
+ @torch.no_grad()
92
+ def _dot(self, tensor1, tensor2, scale=1):
93
+ return torch.sum(torch.mul(tensor1, tensor2), dim=tuple(range(1, tensor1.dim())), keepdim=True) * scale
94
+
95
+ @torch.no_grad()
96
+ def _adam_util(self, prev, grad):
97
+ first_moment = self.beta1 * self.first_m_gamma[self.j] + (1 - self.beta1) * grad
98
+ second_moment = self.beta2 * self.second_m_gamma[self.j] + (1 - self.beta2) * grad**2
99
+ self.first_m_gamma[self.j] = first_moment
100
+ self.second_m_gamma[self.j] = second_moment
101
+ first_moment = first_moment / (1 - self.beta1**self.t)
102
+ second_moment = second_moment / (1 - self.beta2**self.t)
103
+ return prev - self.mu * first_moment / (torch.sqrt(second_moment) + 1e-8)
104
+
105
+ def _update_buffers(self, gamma, c_t=None, denom=None):
106
+ if c_t is None:
107
+ self.first_m_gamma.append(0.0)
108
+ self.second_m_gamma.append(0.0)
109
+ self.gamma_buff.append(gamma)
110
+ self.prev_c.append(0.0)
111
+ self.prev_scale.append(0.0)
112
+ else:
113
+ self.gamma_buff[self.j] = gamma
114
+ self.prev_c[self.j] = c_t
115
+ self.prev_scale[self.j] = denom
116
+
117
+ class SGDP(Optimizer):
118
+ def __init__(self, params, lr=required, momentum=0, dampening=0,
119
+ weight_decay=0, nesterov=False, k=1.0, exclude_set = {}):
120
+ if lr is not required and lr < 0.0:
121
+ raise ValueError("Invalid learning rate: {}".format(lr))
122
+ if momentum < 0.0:
123
+ raise ValueError("Invalid momentum value: {}".format(momentum))
124
+ if weight_decay < 0.0:
125
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
126
+
127
+ defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
128
+ weight_decay=weight_decay, nesterov=nesterov)
129
+ if nesterov and (momentum <= 0 or dampening != 0):
130
+ raise ValueError("Nesterov momentum requires a momentum and zero dampening")
131
+ super(SGDP, self).__init__(params, defaults)
132
+ self.first_iter_flag = False
133
+
134
+ # initialize FTP
135
+ self.ftp = FTP(k, exclude_set=exclude_set)
136
+
137
+
138
+ def __setstate__(self, state):
139
+ super(SGDP, self).__setstate__(state)
140
+ for group in self.param_groups:
141
+ group.setdefault('nesterov', False)
142
+
143
+ @torch.no_grad()
144
+ def step(self, closure=None):
145
+ """Performs a single optimization step.
146
+
147
+ Arguments:
148
+ closure (callable, optional): A closure that reevaluates the model
149
+ and returns the loss.
150
+ """
151
+ loss = None
152
+ if closure is not None:
153
+ with torch.enable_grad():
154
+ loss = closure()
155
+ for group in self.param_groups:
156
+ weight_decay = group['weight_decay']
157
+ momentum = group['momentum']
158
+ dampening = group['dampening']
159
+ nesterov = group['nesterov']
160
+
161
+ for p, name, pre in zip(group['params'],group['name'],group['pre']):
162
+ if p.grad is None:
163
+ continue
164
+ d_p = p.grad
165
+ if weight_decay != 0:
166
+ d_p = d_p.add(p, alpha=weight_decay)
167
+ if momentum != 0:
168
+ param_state = self.state[p]
169
+ if 'momentum_buffer' not in param_state:
170
+ buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
171
+ else:
172
+ buf = param_state['momentum_buffer']
173
+ buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
174
+ if nesterov:
175
+ d_p = d_p.add(buf, alpha=momentum)
176
+ else:
177
+ d_p = buf
178
+
179
+ # FTP step
180
+ d_p = group['lr']*d_p
181
+ new_p = self.ftp.step(name,p,pre,d_p)
182
+ if new_p is not None :
183
+ p.copy_(new_p)
184
+ else:
185
+ p.add_(d_p, alpha=-1)
186
+ # FTP increment internal counters
187
+ self.ftp.incre_counters()
188
+ return loss
189
+
190
+
191
+ class AdamP(Optimizer):
192
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
193
+ weight_decay=0, amsgrad=False, k=1.0, exclude_set={}):
194
+ if not 0.0 <= lr:
195
+ raise ValueError("Invalid learning rate: {}".format(lr))
196
+ if not 0.0 <= eps:
197
+ raise ValueError("Invalid epsilon value: {}".format(eps))
198
+ if not 0.0 <= betas[0] < 1.0:
199
+ raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
200
+ if not 0.0 <= betas[1] < 1.0:
201
+ raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
202
+ if not 0.0 <= weight_decay:
203
+ raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
204
+ defaults = dict(lr=lr, betas=betas, eps=eps,
205
+ weight_decay=weight_decay, amsgrad=amsgrad)
206
+ super(AdamP, self).__init__(params, defaults)
207
+
208
+ # initialize FTP
209
+ self.ftp = FTP(k, exclude_set=exclude_set)
210
+
211
+ def __setstate__(self, state):
212
+ super(AdamP, self).__setstate__(state)
213
+ for group in self.param_groups:
214
+ group.setdefault('amsgrad', False)
215
+
216
+ @torch.no_grad()
217
+ def step(self, closure=None):
218
+ """Performs a single optimization step.
219
+
220
+ Arguments:
221
+ closure (callable, optional): A closure that reevaluates the model
222
+ and returns the loss.
223
+ """
224
+ loss = None
225
+ if closure is not None:
226
+ with torch.enable_grad():
227
+ loss = closure()
228
+
229
+ for group in self.param_groups:
230
+ params_with_grad = []
231
+ grads = []
232
+ exp_avgs = []
233
+ exp_avg_sqs = []
234
+ max_exp_avg_sqs = []
235
+ state_steps = []
236
+
237
+ for p in group['params']:
238
+ if p.grad is not None:
239
+ params_with_grad.append(p)
240
+ if p.grad.is_sparse:
241
+ raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
242
+ grads.append(p.grad)
243
+
244
+ state = self.state[p]
245
+ # Lazy state initialization
246
+ if len(state) == 0:
247
+ state['step'] = 0
248
+ # Exponential moving average of gradient values
249
+ state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)
250
+ # Exponential moving average of squared gradient values
251
+ state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
252
+ if group['amsgrad']:
253
+ # Maintains max of all exp. moving avg. of sq. grad. values
254
+ state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
255
+
256
+ exp_avgs.append(state['exp_avg'])
257
+ exp_avg_sqs.append(state['exp_avg_sq'])
258
+
259
+ if group['amsgrad']:
260
+ max_exp_avg_sqs.append(state['max_exp_avg_sq'])
261
+
262
+ # update the steps for each param group update
263
+ state['step'] += 1
264
+ # record the step after step update
265
+ state_steps.append(state['step'])
266
+
267
+ beta1, beta2 = group['betas']
268
+ self.adam(group,
269
+ exp_avgs,
270
+ exp_avg_sqs,
271
+ max_exp_avg_sqs,
272
+ state_steps,
273
+ group['amsgrad'],
274
+ beta1,
275
+ beta2,
276
+ group['lr'],
277
+ group['weight_decay'],
278
+ group['eps']
279
+ )
280
+ # FTP increment internal counters
281
+ self.ftp.incre_counters()
282
+ return loss
283
+
284
+ def adam(self, group,
285
+ exp_avgs,
286
+ exp_avg_sqs,
287
+ max_exp_avg_sqs,
288
+ state_steps,
289
+ amsgrad: bool,
290
+ beta1: float,
291
+ beta2: float,
292
+ lr: float,
293
+ weight_decay: float,
294
+ eps: float):
295
+
296
+ i = 0
297
+ for param, name, pre in zip(group['params'],group['name'],group['pre']):
298
+ if param.grad is None:
299
+ continue
300
+ grad = param.grad
301
+ exp_avg = exp_avgs[i]
302
+ exp_avg_sq = exp_avg_sqs[i]
303
+ step = state_steps[i]
304
+ if amsgrad:
305
+ max_exp_avg_sq = max_exp_avg_sqs[i]
306
+
307
+ bias_correction1 = 1 - beta1 ** step
308
+ bias_correction2 = 1 - beta2 ** step
309
+
310
+ # Decay the first and second moment running average coefficient
311
+ exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
312
+ exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
313
+ if amsgrad:
314
+ # Maintains the maximum of all 2nd moment running avg. till now
315
+ torch.maximum(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
316
+ # Use the max. for normalizing running avg. of gradient
317
+ denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
318
+ else:
319
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)
320
+
321
+ step_size = lr / bias_correction1
322
+ i += 1
323
+
324
+ # FTP step
325
+ d_p = step_size * exp_avg/denom + lr * weight_decay * param
326
+ new_p = self.ftp.step(name,param,pre,d_p)
327
+ if new_p is None :
328
+ new_p = param - d_p
329
+ param.copy_(new_p)
README.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 💥 Mission: Impossible Language Models 💥
2
+
3
+ This is the code repository for the paper "[Mission: Impossible Language Models](https://arxiv.org/abs/2401.06416)".
4
+
5
+ If you use our code, please cite our paper:
6
+
7
+ ```
8
+ @misc{kallini2024mission,
9
+ title={Mission: Impossible Language Models},
10
+ author={Julie Kallini and Isabel Papadimitriou and Richard Futrell and Kyle Mahowald and Christopher Potts},
11
+ year={2024},
12
+ eprint={2401.06416},
13
+ archivePrefix={arXiv},
14
+ primaryClass={cs.CL}
15
+ }
16
+ ```
17
+
18
+ This repository contains the code necessary to fully replicate our paper, including the scripts to create impossible language datasets, train GPT-2 models, and run all experiments. We also include the notebooks to generate the result graphs in our paper.
19
+
20
+ Let's get started!
21
+
22
+ ## Setup
23
+
24
+ First, clone the repo and install dependencies:
25
+
26
+ ```
27
+ git clone https://github.com/jkallini/mission-impossible-language-models.git
28
+ cd mission-impossible-language-models
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ ## Impossible Language Dataset Creation
33
+
34
+ The scripts for creating impossible language datasets are located in the `data/` directory.
35
+ First, you must download a copy of the [BabyLM dataset](https://babylm.github.io/), which we use for our experiments.
36
+ Then, make sure to set `BABYLM_DATA_PATH` in the `utils.py` file to the path on your system where your BabyLM dataset is located.
37
+
38
+ After downloading the BabyLM dataset, you will need to tag it with morphological features and part-of-speech tags. You can use our `tag.py` script.
39
+
40
+ With the tagged data, you can easily recreate one of the impossible language datasets described in our paper. These are predefined and listed in the `PERTURBATIONS` section at the end of `utils.py`. Here is an
41
+ example for the PartialReverse language from the paper:
42
+
43
+ ```
44
+ python3 perturb.py reverse_partial 100M
45
+ ```
46
+
47
+ This will create a perturbed version of the 100M BabyLM train set. You may use `perturb.py` or `perturb.sh` to perturb multiple splits at the same time.
48
+
49
+ ### Defining New Impossible Languages
50
+
51
+ You can also define your own impossible languages! They are described by four attributes:
52
+
53
+ 1. `perturbation_function`: function mapping tagged sentences to sequences of GPT-2 tokens.
54
+ 2. `affect_function`: function that determines whether an input sentences is "affected" or altered by the perturbation.
55
+ 3. `filter_function`: function that determines whether an input sentence should be included in the final dataset.
56
+ 4. `gpt2_tokenizer`: tokenizer used to perturb this dataset.
57
+
58
+ You can add these definitions to `utils.py`, where the existing perturbations are located.
59
+
60
+
61
+ ## Model Training
62
+
63
+ To train GPT-2 models, we use [`mistral`](https://github.com/stanford-crfm/mistral). If you would like to train GPT-2s with `mistral` as well, please follow their steps for installation. You may download their repo anywhere on your system.
64
+
65
+ Next, make sure to change the following constants in `utils.py`:
66
+ - `CHECKPOINT_WRITE_PATH`: the path where your training checkpoints will be written.
67
+ - `CHECKPOINT_READ_PATH`: the path where you will read training checkpoints when running experiments.
68
+
69
+ Our training scripts are in the `training/` directory.
70
+ Once you have `mistral` installed, set `MISTRAL_PATH` to the path of your library in `prepare_training.sh`. Then, you can use this script to generate the config files that you will use to launch `mistral` training runs.
71
+
72
+ Our scripts will create the config files and move them to the location of your `mistral` directory—you will only need to launch the training run. Here's an example command to launch training for the PartialReverse language using the 100M training set with the random seed set to 41:
73
+
74
+ ```
75
+ CUDA_VISIBLE_DEVICES=0 python3 train.py --config conf/train_reverse_partial_100M_randinit_seed41.yaml --nnodes 1 --nproc_per_node 1 --training_arguments.fp16 true --training_arguments.warmup_steps 300 --training_arguments.max_steps 3000
76
+ ```
77
+
78
+ ## Experiments
79
+
80
+ The main paper includes three experiments: perplexities, surprisals, and causal interventions. The appendix also includes a constituency probing experiment.
81
+
82
+ The scripts to run each of these experiments is separated into their own subdirectories:
83
+
84
+ 1. `perplexities/`: code to run perplexity experiments. You may use `perplexities.py` or `perplexities.sh` to run experiments for multiple languages at the same time.
85
+ 2. `hop_surprisal/`: code to run surprisal experiments for the *Hop languages, in `hop_surprisal.py`.
86
+ 3. `hop_interventions/`: code to run interchange intervention experiments for the *Hop languages. First generate the agreement data using `create_agreement_data.py`, then run the intervention experiments using `hop_interventions.py`.
87
+ You will need to separately clone and install [`align-transformers`](https://github.com/frankaging/align-transformers) (recently renamed to `pyvene`) and set `PATH_TO_ALIGN_TRANSFORMERS` to the path where the library is located on your system.
88
+ 4. `edge_probing/`: code to run constituency probing experiments. Use `get_constituency_parses.py` and `load_phrase_data.py` to prepare the test data, and use `edge_probing.py` to run the experiments.
89
+
90
+ Each directory contains python notebooks to generate the result graphs shown in the paper.
edge_probing/edge_probing.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
edge_probing/edge_probing.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # edge_probing.py
2
+ # Author: Julie Kallini
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ from utils import CHECKPOINT_READ_PATH, PERTURBATIONS, PAREN_MODELS, get_gpt2_tokenizer_with_markers
9
+ from gpt2_no_positional_encoding_model import GPT2NoPositionalEncodingLMHeadModel
10
+ from transformers import GPT2LMHeadModel
11
+ from sklearn.preprocessing import StandardScaler
12
+ from sklearn.linear_model import LogisticRegression
13
+ from sklearn.metrics import accuracy_score
14
+ from sklearn.model_selection import train_test_split
15
+ from itertools import zip_longest
16
+ import torch
17
+ import tqdm
18
+ import argparse
19
+ import pandas as pd
20
+ import os
21
+
22
+
23
+ MAX_TRAINING_STEPS = 3000
24
+ CHECKPOINTS = list(range(200, MAX_TRAINING_STEPS+1, 200))
25
+ LAYERS = [1, 3, 6, 9, 12, "Avg Last 4"]
26
+
27
+
28
+ def get_layer_embedding(model, token_sequences, indices, layer=None):
29
+
30
+ # Pad input token sequences
31
+ input_ids = zip(*zip_longest(*token_sequences,
32
+ fillvalue=gpt2_tokenizer.eos_token_id))
33
+ input_ids = torch.tensor(list(input_ids)).to(device)
34
+
35
+ # Get GPT2 model's output
36
+ with torch.no_grad():
37
+ output = model(input_ids)
38
+
39
+ # Either get the hidden state of the specified layer or
40
+ # get the average of the last 4 hidden states
41
+ if layer is not None:
42
+ hidden_states = output.hidden_states[layer]
43
+ else:
44
+ hidden_states = output.hidden_states[-4:]
45
+ hidden_states = sum(hidden_states) / 4
46
+
47
+ # Create mask using start and end indices
48
+ batch_size, seq_length = input_ids.shape
49
+ mask = torch.full((batch_size, seq_length), 0).to(device)
50
+ for i, (start_idx, end_idx) in enumerate(indices):
51
+ mask[i, start_idx:end_idx] = 1
52
+
53
+ # Mask out embeddings of tokens outside indices
54
+ mask_expanded = mask.unsqueeze(-1).expand(hidden_states.size())
55
+ hidden_states = hidden_states * mask_expanded
56
+
57
+ return hidden_states
58
+
59
+
60
+ def max_pooling(tensor, index_tuples):
61
+ pooled_results = []
62
+ for i, (start, end) in enumerate(index_tuples):
63
+ # Extracting the embeddings corresponding to the specified range
64
+ embeddings = tensor[i, start:end, :]
65
+
66
+ # Performing max pooling
67
+ max_pooled = torch.max(embeddings, dim=0)[0]
68
+
69
+ pooled_results.append(max_pooled)
70
+ return torch.stack(pooled_results)
71
+
72
+
73
+ def mean_pooling(tensor, index_tuples):
74
+ batch_size, seq_len, embedding_size = tensor.shape
75
+ output = torch.empty(batch_size, embedding_size,
76
+ device=tensor.device, dtype=tensor.dtype)
77
+
78
+ for i, (start, end) in enumerate(index_tuples):
79
+ embeddings = tensor[i, start:end, :]
80
+ output[i, :] = torch.mean(embeddings, dim=0)
81
+
82
+ return output
83
+
84
+
85
+ if __name__ == "__main__":
86
+ parser = argparse.ArgumentParser(
87
+ prog='Edge probing',
88
+ description='Edge probing experiments')
89
+ parser.add_argument('perturbation_type',
90
+ default='all',
91
+ const='all',
92
+ nargs='?',
93
+ choices=PERTURBATIONS.keys(),
94
+ help='Perturbation function used to transform BabyLM dataset')
95
+ parser.add_argument('train_set',
96
+ default='all',
97
+ const='all',
98
+ nargs='?',
99
+ choices=["100M", "10M"],
100
+ help='BabyLM train set')
101
+ parser.add_argument('random_seed', type=int, help="Random seed")
102
+ parser.add_argument('paren_model',
103
+ default='all',
104
+ const='all',
105
+ nargs='?',
106
+ choices=list(PAREN_MODELS.keys()) + ["randinit"],
107
+ help='Parenthesis model')
108
+ parser.add_argument('pooling_operation',
109
+ default='all',
110
+ const='all',
111
+ nargs='?',
112
+ choices=["mean", "max"],
113
+ help='Pooling operation to compute on embeddings')
114
+ parser.add_argument('-np', '--no_pos_encodings', action='store_true',
115
+ help="Train GPT-2 with no positional encodings")
116
+
117
+ # Get args
118
+ args = parser.parse_args()
119
+
120
+ if args.pooling_operation == "mean":
121
+ pooling_function = mean_pooling
122
+ elif args.pooling_operation == "max":
123
+ pooling_function = max_pooling
124
+ else:
125
+ raise Exception("Pooling operation undefined")
126
+
127
+ # Init tokenizer
128
+ gpt2_tokenizer = get_gpt2_tokenizer_with_markers([])
129
+ gpt2_tokenizer.pad_token = gpt2_tokenizer.eos_token
130
+
131
+ # Get path to model
132
+ no_pos_encodings_underscore = "_no_positional_encodings" if args.no_pos_encodings else ""
133
+ model = f"babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}_seed{args.random_seed}"
134
+ model_path = f"{CHECKPOINT_READ_PATH}/babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}/{model}/runs/{model}/checkpoint-"
135
+
136
+ # Get constituency parse data
137
+ if "hop" in args.perturbation_type:
138
+ phrase_df = pd.read_csv("phrase_data/hop_phrase_data.csv")
139
+ elif "reverse" in args.perturbation_type:
140
+ phrase_df = pd.read_csv("phrase_data/reverse_phrase_data.csv")
141
+ else:
142
+ raise Exception("Phrase data not found")
143
+
144
+ token_sequences = list(phrase_df["Sentence Tokens"])
145
+ if args.perturbation_type == "reverse_full":
146
+ indices = list(
147
+ zip(phrase_df["Rev Start Index"], phrase_df["Rev End Index"]))
148
+ else:
149
+ indices = list(zip(phrase_df["Start Index"], phrase_df["End Index"]))
150
+ labels = list(phrase_df["Category"])
151
+
152
+ BATCH_SIZE = 32
153
+ device = "cuda"
154
+
155
+ edge_probing_df = pd.DataFrame(LAYERS, columns=["GPT-2 Layer"])
156
+ for ckpt in CHECKPOINTS:
157
+
158
+ # Load model
159
+ if args.no_pos_encodings:
160
+ model = GPT2LMHeadModel.from_pretrained(
161
+ model_path + str(ckpt), output_hidden_states=True).to(device)
162
+ else:
163
+ model = GPT2NoPositionalEncodingLMHeadModel.from_pretrained(
164
+ model_path + str(ckpt), output_hidden_states=True).to(device)
165
+
166
+ layer_accuracies = []
167
+ for layer in LAYERS:
168
+ print(f"Checkpoint: {ckpt}, Layer: {layer}")
169
+ print("Computing span embeddings...")
170
+
171
+ # Iterate over token sequences and indices to get embeddings
172
+ spans = []
173
+ for i in tqdm.tqdm(list(range(0, len(token_sequences), BATCH_SIZE))):
174
+
175
+ tokens_batch = [[int(tok) for tok in seq.split()]
176
+ for seq in token_sequences[i:i+BATCH_SIZE]]
177
+ if args.perturbation_type == "reverse_full":
178
+ tokens_batch = [toks[::-1] for toks in tokens_batch]
179
+
180
+ index_batch = indices[i:i+BATCH_SIZE]
181
+
182
+ # Extract embeddings
183
+ if layer == "Avg Last 4":
184
+ embeddings = get_layer_embedding(
185
+ model, tokens_batch, index_batch, None)
186
+ else:
187
+ embeddings = get_layer_embedding(
188
+ model, tokens_batch, index_batch, layer)
189
+ pooled_results = pooling_function(embeddings, index_batch)
190
+ spans.extend(list(pooled_results))
191
+
192
+ # Get features and ground truth
193
+ X = torch.vstack(spans).detach().cpu().numpy()
194
+ y = labels
195
+
196
+ # Split the data; since we pass random seed, it
197
+ # will be the same split every time
198
+ X_train, X_test, y_train, y_test = train_test_split(
199
+ X, y, test_size=0.2, random_state=args.random_seed)
200
+
201
+ # Fit L2-regularized linear classifier
202
+ clf = LogisticRegression(max_iter=10,
203
+ random_state=args.random_seed).fit(X_train, y_train)
204
+
205
+ # Get probe accuracy
206
+ y_test_pred = clf.predict(X_test)
207
+ acc = accuracy_score(y_test, y_test_pred)
208
+ layer_accuracies.append(acc)
209
+ print(f"Accuracy: {acc}")
210
+
211
+ edge_probing_df[f"Accuracy (ckpt {ckpt})"] = layer_accuracies
212
+
213
+ # Write results to CSV
214
+ nps = '_no_pos_encodings' if args.no_pos_encodings else ''
215
+ directory = f"edge_probing_results/{args.perturbation_type}_{args.train_set}{nps}"
216
+ if not os.path.exists(directory):
217
+ os.makedirs(directory)
218
+
219
+ file = directory + \
220
+ f"/{args.paren_model}_{args.pooling_operation}_pooling_seed{args.random_seed}.csv"
221
+ print(f"Writing results to CSV: {file}")
222
+ edge_probing_df.to_csv(file)
edge_probing/edge_probing.sh ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ # edge_probing.sh
3
+ # author: Julie Kallini
4
+
5
+ echo "
6
+ -------------------------------------------------------------------------------
7
+ Arguments
8
+ -------------------------------------------------------------------------------
9
+ "
10
+ echo "Random seed: $1"
11
+ NO_POS_ENCODINGS=${2:-''}
12
+ echo "No pos encodings: $NO_POS_ENCODINGS"
13
+
14
+ echo "
15
+ -------------------------------------------------------------------------------
16
+ Run edge probing for each perturbation type
17
+ -------------------------------------------------------------------------------
18
+ "
19
+
20
+ COMMAND="python3 edge_probing.py hop_control 100M $1 randinit mean $NO_POS_ENCODINGS"
21
+ echo $COMMAND
22
+ eval $COMMAND
23
+ echo "
24
+ "
25
+
26
+ COMMAND="python3 edge_probing.py hop_tokens4 100M $1 randinit mean $NO_POS_ENCODINGS"
27
+ echo $COMMAND
28
+ eval $COMMAND
29
+ echo "
30
+ "
31
+
32
+ COMMAND="python3 edge_probing.py hop_words4 100M $1 randinit mean $NO_POS_ENCODINGS"
33
+ echo $COMMAND
34
+ eval $COMMAND
35
+ echo "
36
+ "
37
+
38
+ COMMAND="python3 edge_probing.py reverse_control 100M $1 randinit mean $NO_POS_ENCODINGS"
39
+ echo $COMMAND
40
+ eval $COMMAND
41
+ echo "
42
+ "
43
+
44
+ COMMAND="python3 edge_probing.py reverse_full 100M $1 randinit mean $NO_POS_ENCODINGS"
45
+ echo $COMMAND
46
+ eval $COMMAND
47
+ echo "
48
+ "
49
+
50
+ COMMAND="python3 edge_probing.py reverse_partial 100M $1 randinit mean $NO_POS_ENCODINGS"
51
+ echo $COMMAND
52
+ eval $COMMAND
53
+ echo "
54
+ "
55
+
56
+ echo "
57
+ -------------------------------------------------------------------------------
58
+ Done!
59
+ -------------------------------------------------------------------------------
60
+ "
gpt2_no_positional_encoding_model.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # gpt2_no_positional_encoding_model.py
2
+ # Adapted from Huggingface's transformers library
3
+
4
+ import torch
5
+ from transformers.models.gpt2.modeling_gpt2 import GPT2Block, GPT2PreTrainedModel
6
+ from transformers.modeling_outputs import CausalLMOutputWithCrossAttentions, BaseModelOutputWithPastAndCrossAttentions
7
+ from transformers.utils.model_parallel_utils import assert_device_map, get_device_map
8
+ from torch import nn
9
+ from torch.nn import CrossEntropyLoss
10
+ from typing import Optional, Tuple, Union
11
+
12
+ class GPT2NoPositionalEncodingModel(GPT2PreTrainedModel):
13
+ def __init__(self, config):
14
+ super().__init__(config)
15
+
16
+ self.embed_dim = config.hidden_size
17
+
18
+ self.wte = nn.Embedding(config.vocab_size, self.embed_dim)
19
+
20
+ self.drop = nn.Dropout(config.embd_pdrop)
21
+ self.h = nn.ModuleList([GPT2Block(config, layer_idx=i) for i in range(config.num_hidden_layers)])
22
+ self.ln_f = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_epsilon)
23
+
24
+ # Model parallel
25
+ self.model_parallel = False
26
+ self.device_map = None
27
+ self.gradient_checkpointing = False
28
+
29
+ # Initialize weights and apply final processing
30
+ self.post_init()
31
+
32
+ def parallelize(self, device_map=None):
33
+ # Check validity of device_map
34
+ self.device_map = (
35
+ get_device_map(len(self.h), range(torch.cuda.device_count())) if device_map is None else device_map
36
+ )
37
+ assert_device_map(self.device_map, len(self.h))
38
+ self.model_parallel = True
39
+ self.first_device = "cpu" if "cpu" in self.device_map.keys() else "cuda:" + str(min(self.device_map.keys()))
40
+ self.last_device = "cuda:" + str(max(self.device_map.keys()))
41
+ self.wte = self.wte.to(self.first_device)
42
+ # Load onto devices
43
+ for k, v in self.device_map.items():
44
+ for block in v:
45
+ cuda_device = "cuda:" + str(k)
46
+ self.h[block] = self.h[block].to(cuda_device)
47
+ # ln_f to last
48
+ self.ln_f = self.ln_f.to(self.last_device)
49
+
50
+ def deparallelize(self):
51
+ self.model_parallel = False
52
+ self.device_map = None
53
+ self.first_device = "cpu"
54
+ self.last_device = "cpu"
55
+ self.wte = self.wte.to("cpu")
56
+ for index in range(len(self.h)):
57
+ self.h[index] = self.h[index].to("cpu")
58
+ self.ln_f = self.ln_f.to("cpu")
59
+ torch.cuda.empty_cache()
60
+
61
+ def get_input_embeddings(self):
62
+ return self.wte
63
+
64
+ def set_input_embeddings(self, new_embeddings):
65
+ self.wte = new_embeddings
66
+
67
+ def _prune_heads(self, heads_to_prune):
68
+ """
69
+ Prunes heads of the model. heads_to_prune: dict of {layer_num: list of heads to prune in this layer}
70
+ """
71
+ for layer, heads in heads_to_prune.items():
72
+ self.h[layer].attn.prune_heads(heads)
73
+
74
+ def forward(
75
+ self,
76
+ input_ids: Optional[torch.LongTensor] = None,
77
+ past_key_values: Optional[Tuple[Tuple[torch.Tensor]]] = None,
78
+ attention_mask: Optional[torch.FloatTensor] = None,
79
+ token_type_ids: Optional[torch.LongTensor] = None,
80
+ position_ids: Optional[torch.LongTensor] = None,
81
+ head_mask: Optional[torch.FloatTensor] = None,
82
+ inputs_embeds: Optional[torch.FloatTensor] = None,
83
+ encoder_hidden_states: Optional[torch.Tensor] = None,
84
+ encoder_attention_mask: Optional[torch.FloatTensor] = None,
85
+ use_cache: Optional[bool] = None,
86
+ output_attentions: Optional[bool] = None,
87
+ output_hidden_states: Optional[bool] = None,
88
+ return_dict: Optional[bool] = None,
89
+ ) -> Union[Tuple, BaseModelOutputWithPastAndCrossAttentions]:
90
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
91
+ output_hidden_states = (
92
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
93
+ )
94
+ use_cache = use_cache if use_cache is not None else self.config.use_cache
95
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
96
+
97
+ if input_ids is not None and inputs_embeds is not None:
98
+ raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
99
+ elif input_ids is not None:
100
+ input_shape = input_ids.size()
101
+ input_ids = input_ids.view(-1, input_shape[-1])
102
+ batch_size = input_ids.shape[0]
103
+ elif inputs_embeds is not None:
104
+ input_shape = inputs_embeds.size()[:-1]
105
+ batch_size = inputs_embeds.shape[0]
106
+ else:
107
+ raise ValueError("You have to specify either input_ids or inputs_embeds")
108
+
109
+ device = input_ids.device if input_ids is not None else inputs_embeds.device
110
+
111
+ if token_type_ids is not None:
112
+ token_type_ids = token_type_ids.view(-1, input_shape[-1])
113
+
114
+ if past_key_values is None:
115
+ past_length = 0
116
+ past_key_values = tuple([None] * len(self.h))
117
+ else:
118
+ past_length = past_key_values[0][0].size(-2)
119
+ if position_ids is None:
120
+ position_ids = torch.arange(past_length, input_shape[-1] + past_length, dtype=torch.long, device=device)
121
+ position_ids = position_ids.unsqueeze(0)
122
+
123
+ # GPT2Attention mask.
124
+ if attention_mask is not None:
125
+ if batch_size <= 0:
126
+ raise ValueError("batch_size has to be defined and > 0")
127
+ attention_mask = attention_mask.view(batch_size, -1)
128
+ # We create a 3D attention mask from a 2D tensor mask.
129
+ # Sizes are [batch_size, 1, 1, to_seq_length]
130
+ # So we can broadcast to [batch_size, num_heads, from_seq_length, to_seq_length]
131
+ # this attention mask is more simple than the triangular masking of causal attention
132
+ # used in OpenAI GPT, we just need to prepare the broadcast dimension here.
133
+ attention_mask = attention_mask[:, None, None, :]
134
+
135
+ # Since attention_mask is 1.0 for positions we want to attend and 0.0 for
136
+ # masked positions, this operation will create a tensor which is 0.0 for
137
+ # positions we want to attend and the dtype's smallest value for masked positions.
138
+ # Since we are adding it to the raw scores before the softmax, this is
139
+ # effectively the same as removing these entirely.
140
+ attention_mask = attention_mask.to(dtype=self.dtype) # fp16 compatibility
141
+ attention_mask = (1.0 - attention_mask) * torch.finfo(self.dtype).min
142
+
143
+ # If a 2D or 3D attention mask is provided for the cross-attention
144
+ # we need to make broadcastable to [batch_size, num_heads, seq_length, seq_length]
145
+ if self.config.add_cross_attention and encoder_hidden_states is not None:
146
+ encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size()
147
+ encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length)
148
+ if encoder_attention_mask is None:
149
+ encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device)
150
+ encoder_attention_mask = self.invert_attention_mask(encoder_attention_mask)
151
+ else:
152
+ encoder_attention_mask = None
153
+
154
+ # Prepare head mask if needed
155
+ # 1.0 in head_mask indicate we keep the head
156
+ # attention_probs has shape bsz x n_heads x N x N
157
+ # head_mask has shape n_layer x batch x n_heads x N x N
158
+ head_mask = self.get_head_mask(head_mask, self.config.n_layer)
159
+
160
+ if inputs_embeds is None:
161
+ inputs_embeds = self.wte(input_ids)
162
+ hidden_states = inputs_embeds
163
+
164
+ if token_type_ids is not None:
165
+ token_type_embeds = self.wte(token_type_ids)
166
+ hidden_states = hidden_states + token_type_embeds
167
+
168
+ hidden_states = self.drop(hidden_states)
169
+
170
+ output_shape = (-1,) + input_shape[1:] + (hidden_states.size(-1),)
171
+
172
+ if self.gradient_checkpointing and self.training:
173
+ if use_cache:
174
+ use_cache = False
175
+
176
+ presents = () if use_cache else None
177
+ all_self_attentions = () if output_attentions else None
178
+ all_cross_attentions = () if output_attentions and self.config.add_cross_attention else None
179
+ all_hidden_states = () if output_hidden_states else None
180
+ for i, (block, layer_past) in enumerate(zip(self.h, past_key_values)):
181
+ # Model parallel
182
+ if self.model_parallel:
183
+ torch.cuda.set_device(hidden_states.device)
184
+ # Ensure layer_past is on same device as hidden_states (might not be correct)
185
+ if layer_past is not None:
186
+ layer_past = tuple(past_state.to(hidden_states.device) for past_state in layer_past)
187
+ # Ensure that attention_mask is always on the same device as hidden_states
188
+ if attention_mask is not None:
189
+ attention_mask = attention_mask.to(hidden_states.device)
190
+ if isinstance(head_mask, torch.Tensor):
191
+ head_mask = head_mask.to(hidden_states.device)
192
+ if output_hidden_states:
193
+ all_hidden_states = all_hidden_states + (hidden_states,)
194
+
195
+ if self.gradient_checkpointing and self.training:
196
+ outputs = self._gradient_checkpointing_func(
197
+ block.__call__,
198
+ hidden_states,
199
+ None,
200
+ attention_mask,
201
+ head_mask[i],
202
+ encoder_hidden_states,
203
+ encoder_attention_mask,
204
+ use_cache,
205
+ output_attentions,
206
+ )
207
+ else:
208
+ outputs = block(
209
+ hidden_states,
210
+ layer_past=layer_past,
211
+ attention_mask=attention_mask,
212
+ head_mask=head_mask[i],
213
+ encoder_hidden_states=encoder_hidden_states,
214
+ encoder_attention_mask=encoder_attention_mask,
215
+ use_cache=use_cache,
216
+ output_attentions=output_attentions,
217
+ )
218
+
219
+ hidden_states = outputs[0]
220
+ if use_cache is True:
221
+ presents = presents + (outputs[1],)
222
+
223
+ if output_attentions:
224
+ all_self_attentions = all_self_attentions + (outputs[2 if use_cache else 1],)
225
+ if self.config.add_cross_attention:
226
+ all_cross_attentions = all_cross_attentions + (outputs[3 if use_cache else 2],)
227
+
228
+ # Model Parallel: If it's the last layer for that device, put things on the next device
229
+ if self.model_parallel:
230
+ for k, v in self.device_map.items():
231
+ if i == v[-1] and "cuda:" + str(k) != self.last_device:
232
+ hidden_states = hidden_states.to("cuda:" + str(k + 1))
233
+
234
+ hidden_states = self.ln_f(hidden_states)
235
+
236
+ hidden_states = hidden_states.view(output_shape)
237
+ # Add last hidden state
238
+ if output_hidden_states:
239
+ all_hidden_states = all_hidden_states + (hidden_states,)
240
+
241
+ if not return_dict:
242
+ return tuple(
243
+ v
244
+ for v in [hidden_states, presents, all_hidden_states, all_self_attentions, all_cross_attentions]
245
+ if v is not None
246
+ )
247
+
248
+ return BaseModelOutputWithPastAndCrossAttentions(
249
+ last_hidden_state=hidden_states,
250
+ past_key_values=presents,
251
+ hidden_states=all_hidden_states,
252
+ attentions=all_self_attentions,
253
+ cross_attentions=all_cross_attentions,
254
+ )
255
+
256
+ class GPT2NoPositionalEncodingLMHeadModel(GPT2PreTrainedModel):
257
+ _tied_weights_keys = ["lm_head.weight"]
258
+
259
+ def __init__(self, config):
260
+ super().__init__(config)
261
+ self.transformer = GPT2NoPositionalEncodingModel(config)
262
+ self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
263
+
264
+ # Model parallel
265
+ self.model_parallel = False
266
+ self.device_map = None
267
+
268
+ # Initialize weights and apply final processing
269
+ self.post_init()
270
+
271
+ def parallelize(self, device_map=None):
272
+ self.device_map = (
273
+ get_device_map(len(self.transformer.h), range(torch.cuda.device_count()))
274
+ if device_map is None
275
+ else device_map
276
+ )
277
+ assert_device_map(self.device_map, len(self.transformer.h))
278
+ self.transformer.parallelize(self.device_map)
279
+ self.lm_head = self.lm_head.to(self.transformer.first_device)
280
+ self.model_parallel = True
281
+
282
+ def deparallelize(self):
283
+ self.transformer.deparallelize()
284
+ self.transformer = self.transformer.to("cpu")
285
+ self.lm_head = self.lm_head.to("cpu")
286
+ self.model_parallel = False
287
+ torch.cuda.empty_cache()
288
+
289
+ def get_output_embeddings(self):
290
+ return self.lm_head
291
+
292
+ def set_output_embeddings(self, new_embeddings):
293
+ self.lm_head = new_embeddings
294
+
295
+ def prepare_inputs_for_generation(self, input_ids, past_key_values=None, inputs_embeds=None, **kwargs):
296
+ token_type_ids = kwargs.get("token_type_ids", None)
297
+ # Omit tokens covered by past_key_values
298
+ if past_key_values:
299
+ past_length = past_key_values[0][0].shape[2]
300
+
301
+ # Some generation methods already pass only the last input ID
302
+ if input_ids.shape[1] > past_length:
303
+ remove_prefix_length = past_length
304
+ else:
305
+ # Default to old behavior: keep only final ID
306
+ remove_prefix_length = input_ids.shape[1] - 1
307
+
308
+ input_ids = input_ids[:, remove_prefix_length:]
309
+ if token_type_ids is not None:
310
+ token_type_ids = token_type_ids[:, -input_ids.shape[1] :]
311
+
312
+ attention_mask = kwargs.get("attention_mask", None)
313
+ position_ids = kwargs.get("position_ids", None)
314
+
315
+ if attention_mask is not None and position_ids is None:
316
+ # create position_ids on the fly for batch generation
317
+ position_ids = attention_mask.long().cumsum(-1) - 1
318
+ position_ids.masked_fill_(attention_mask == 0, 1)
319
+ if past_key_values:
320
+ position_ids = position_ids[:, -input_ids.shape[1] :]
321
+ else:
322
+ position_ids = None
323
+
324
+ # if `inputs_embeds` are passed, we only want to use them in the 1st generation step
325
+ if inputs_embeds is not None and past_key_values is None:
326
+ model_inputs = {"inputs_embeds": inputs_embeds}
327
+ else:
328
+ model_inputs = {"input_ids": input_ids}
329
+
330
+ model_inputs.update(
331
+ {
332
+ "past_key_values": past_key_values,
333
+ "use_cache": kwargs.get("use_cache"),
334
+ "position_ids": position_ids,
335
+ "attention_mask": attention_mask,
336
+ "token_type_ids": token_type_ids,
337
+ }
338
+ )
339
+
340
+ return model_inputs
341
+
342
+ def forward(
343
+ self,
344
+ input_ids: Optional[torch.LongTensor] = None,
345
+ past_key_values: Optional[Tuple[Tuple[torch.Tensor]]] = None,
346
+ attention_mask: Optional[torch.FloatTensor] = None,
347
+ token_type_ids: Optional[torch.LongTensor] = None,
348
+ position_ids: Optional[torch.LongTensor] = None,
349
+ head_mask: Optional[torch.FloatTensor] = None,
350
+ inputs_embeds: Optional[torch.FloatTensor] = None,
351
+ encoder_hidden_states: Optional[torch.Tensor] = None,
352
+ encoder_attention_mask: Optional[torch.FloatTensor] = None,
353
+ labels: Optional[torch.LongTensor] = None,
354
+ use_cache: Optional[bool] = None,
355
+ output_attentions: Optional[bool] = None,
356
+ output_hidden_states: Optional[bool] = None,
357
+ return_dict: Optional[bool] = None,
358
+ ) -> Union[Tuple, CausalLMOutputWithCrossAttentions]:
359
+ r"""
360
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
361
+ Labels for language modeling. Note that the labels **are shifted** inside the model, i.e. you can set
362
+ `labels = input_ids` Indices are selected in `[-100, 0, ..., config.vocab_size]` All labels set to `-100`
363
+ are ignored (masked), the loss is only computed for labels in `[0, ..., config.vocab_size]`
364
+ """
365
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
366
+
367
+ transformer_outputs = self.transformer(
368
+ input_ids,
369
+ past_key_values=past_key_values,
370
+ attention_mask=attention_mask,
371
+ token_type_ids=token_type_ids,
372
+ position_ids=position_ids,
373
+ head_mask=head_mask,
374
+ inputs_embeds=inputs_embeds,
375
+ encoder_hidden_states=encoder_hidden_states,
376
+ encoder_attention_mask=encoder_attention_mask,
377
+ use_cache=use_cache,
378
+ output_attentions=output_attentions,
379
+ output_hidden_states=output_hidden_states,
380
+ return_dict=return_dict,
381
+ )
382
+ hidden_states = transformer_outputs[0]
383
+
384
+ # Set device for model parallelism
385
+ if self.model_parallel:
386
+ torch.cuda.set_device(self.transformer.first_device)
387
+ hidden_states = hidden_states.to(self.lm_head.weight.device)
388
+
389
+ lm_logits = self.lm_head(hidden_states)
390
+
391
+ loss = None
392
+ if labels is not None:
393
+ # move labels to correct device to enable model parallelism
394
+ labels = labels.to(lm_logits.device)
395
+ # Shift so that tokens < n predict n
396
+ shift_logits = lm_logits[..., :-1, :].contiguous()
397
+ shift_labels = labels[..., 1:].contiguous()
398
+ # Flatten the tokens
399
+ loss_fct = CrossEntropyLoss()
400
+ loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
401
+
402
+ if not return_dict:
403
+ output = (lm_logits,) + transformer_outputs[1:]
404
+ return ((loss,) + output) if loss is not None else output
405
+
406
+ return CausalLMOutputWithCrossAttentions(
407
+ loss=loss,
408
+ logits=lm_logits,
409
+ past_key_values=transformer_outputs.past_key_values,
410
+ hidden_states=transformer_outputs.hidden_states,
411
+ attentions=transformer_outputs.attentions,
412
+ cross_attentions=transformer_outputs.cross_attentions,
413
+ )
414
+
415
+ @staticmethod
416
+ def _reorder_cache(
417
+ past_key_values: Tuple[Tuple[torch.Tensor]], beam_idx: torch.Tensor
418
+ ) -> Tuple[Tuple[torch.Tensor]]:
419
+ """
420
+ This function is used to re-order the `past_key_values` cache if [`~PreTrainedModel.beam_search`] or
421
+ [`~PreTrainedModel.beam_sample`] is called. This is required to match `past_key_values` with the correct
422
+ beam_idx at every generation step.
423
+ """
424
+ return tuple(
425
+ tuple(past_state.index_select(0, beam_idx.to(past_state.device)) for past_state in layer_past)
426
+ for layer_past in past_key_values
427
+ )
hop_interventions/create_agreement_data.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # create__agreement_data.py
2
+ # Author: Julie Kallini
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ import pandas as pd
9
+ from glob import glob
10
+ import re
11
+ from utils import gpt2_hop_tokenizer, BABYLM_DATA_PATH, marker_sg_token
12
+ from pluralizer import Pluralizer
13
+
14
+
15
+ if __name__ == "__main__":
16
+
17
+ get_vocab_dict = []
18
+ vocab = gpt2_hop_tokenizer.vocab
19
+
20
+ # Patterns for finding simple "The SUBJ VERB x x x x MARKER" sequences
21
+ control_pattern = f"(?:^| )(?:{vocab['The']}|{vocab['Ġthe']}) [1-9]+ [1-9]+ {marker_sg_token} [1-9]+ [1-9]+ [1-9]+ [1-9]+"
22
+ words4_pattern = f"(?:^| )(?:{vocab['The']}|{vocab['Ġthe']}) [1-9]+ [1-9]+ [1-9]+ [1-9]+ [1-9]+ [1-9]+ {marker_sg_token}"
23
+
24
+ # Get test file paths
25
+ test_file_path = f'{BABYLM_DATA_PATH}/babylm_data_perturbed/' + \
26
+ 'babylm_hop_{}/babylm_test_affected/*'
27
+ control_files = sorted(glob(test_file_path.format("control")))
28
+ words4_files = sorted(glob(test_file_path.format("words4")))
29
+
30
+ # Iterate over files and get candidate sequences for interventions
31
+ candidate_sequences = []
32
+ for control_file_path, words4_file_path in zip(control_files, words4_files):
33
+ print(control_file_path.split("/")[-1])
34
+ assert control_file_path.split(
35
+ "/")[-1] == words4_file_path.split("/")[-1]
36
+
37
+ # Iterate over pairs of lines in file (control and words4)
38
+ control_lines = open(control_file_path, 'r').readlines()
39
+ words4_lines = open(words4_file_path, 'r').readlines()
40
+ for cl, wl in zip(control_lines, words4_lines):
41
+
42
+ # Find all matches to patterns
43
+ cseqs = re.findall(control_pattern, cl)
44
+ wseqs = re.findall(words4_pattern, wl)
45
+
46
+ # See if there is a shared pattern in control and words4 line
47
+ for cseq, wseq in zip(re.findall(control_pattern, cl), re.findall(words4_pattern, wl)):
48
+ if cseq.replace(" " + str(marker_sg_token), "") == wseq.replace(" " + str(marker_sg_token), ""):
49
+ candidate_sequences.append(
50
+ [int(s) for s in wseq.replace(
51
+ str(marker_sg_token), "").split()]
52
+ )
53
+
54
+ # Init pluralizer
55
+ pluralizer = Pluralizer()
56
+
57
+ # Iterate over candidate sequences
58
+ data = []
59
+ for seq in candidate_sequences:
60
+
61
+ # Get string version of sequence to get the subject
62
+ splitted = gpt2_hop_tokenizer.decode(seq).split()
63
+
64
+ # Get plural of subject (the word at index 1)
65
+ splitted[1] = pluralizer.pluralize(splitted[1], 2, False)
66
+
67
+ # Get GPT-2 tokens of plural version of sequence
68
+ plur_seq = gpt2_hop_tokenizer.encode(" ".join(splitted))
69
+
70
+ # If new subject form has a different number of tokens, skip
71
+ if len(plur_seq) != len(seq):
72
+ continue
73
+
74
+ # In case " the" has changed to "the", reset the first token
75
+ plur_seq[0] = seq[0]
76
+
77
+ # If singular and plural sequence are the same, skip
78
+ if seq == plur_seq:
79
+ continue
80
+
81
+ data.append([" ".join([str(s) for s in seq]),
82
+ " ".join([str(s) for s in plur_seq])])
83
+
84
+ df = pd.DataFrame(data, columns=["Singular", "Plural"])
85
+ df.to_csv("hop_agreement_data.csv")
hop_interventions/hop_interventions.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
hop_interventions/hop_interventions.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # hop_interventions.py
2
+ # Author: Julie Kallini
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ # align-transformers
9
+ PATH_TO_ALIGN_TRANSFORMERS = "/nlp/scr/kallini/align-transformers/"
10
+ sys.path.append(PATH_TO_ALIGN_TRANSFORMERS)
11
+
12
+ import pandas as pd
13
+ from models.utils import embed_to_distrib
14
+ from models.configuration_alignable_model import AlignableRepresentationConfig, AlignableConfig
15
+ from models.alignable_base import AlignableModel
16
+ from models.interventions import VanillaIntervention
17
+ from utils import CHECKPOINT_READ_PATH, marker_sg_token, marker_pl_token, \
18
+ PERTURBATIONS, PAREN_MODELS
19
+ from tqdm import tqdm
20
+ from transformers import GPT2Model
21
+ from gpt2_no_positional_encoding_model import GPT2NoPositionalEncodingModel
22
+ import os
23
+ import torch
24
+ import argparse
25
+
26
+
27
+ MAX_TRAINING_STEPS = 3000
28
+ CHECKPOINTS = list(range(100, MAX_TRAINING_STEPS+1, 100))
29
+
30
+
31
+ def simple_position_config(model_type, intervention_type, layer):
32
+ alignable_config = AlignableConfig(
33
+ alignable_model_type=model_type,
34
+ alignable_representations=[
35
+ AlignableRepresentationConfig(
36
+ layer, # layer
37
+ intervention_type, # intervention type
38
+ "pos", # intervention unit
39
+ 1 # max number of unit
40
+ ),
41
+ ],
42
+ alignable_interventions_type=VanillaIntervention,
43
+ )
44
+ return alignable_config
45
+
46
+
47
+ def get_model(perturbation_type, train_set, seed, paren_model, ckpt, no_pos_encodings=False):
48
+
49
+ # Get path to model
50
+ no_pos_encodings = "_no_positional_encodings" if no_pos_encodings else ""
51
+ model = f"babylm_{perturbation_type}_{train_set}_{paren_model}{no_pos_encodings}_seed{seed}"
52
+ model_path = f"{CHECKPOINT_READ_PATH}/babylm_{perturbation_type}_{train_set}_{paren_model}{no_pos_encodings}/{model}/runs/{model}/checkpoint-{ckpt}"
53
+
54
+ # Load appropriate GPT-2 model
55
+ if no_pos_encodings:
56
+ return GPT2NoPositionalEncodingModel.from_pretrained(model_path).to(device)
57
+ else:
58
+ return GPT2Model.from_pretrained(model_path).to(device)
59
+
60
+
61
+ def run_interventions(model, base_input_ids, source_input_ids):
62
+
63
+ tokens = [marker_sg_token, marker_pl_token]
64
+
65
+ data = []
66
+ BATCH_SIZE = 16
67
+ for batch_i in tqdm(range(0, len(base_input_ids), BATCH_SIZE)):
68
+
69
+ # Get base and source batches
70
+ base_batch = base_input_ids[batch_i:batch_i+BATCH_SIZE]
71
+ source_batch = source_input_ids[batch_i:batch_i+BATCH_SIZE]
72
+
73
+ # Iterate over GPT-2 layers
74
+ for layer_i in range(model.config.n_layer):
75
+
76
+ # Get block_output config for this layer
77
+ alignable_config = simple_position_config(
78
+ type(model), "block_output", layer_i)
79
+ alignable = AlignableModel(alignable_config, model)
80
+
81
+ # Iterate over token positions
82
+ for pos_i in range(len(base_batch[0])):
83
+
84
+ _, counterfactual_outputs = alignable(
85
+ {"input_ids": torch.tensor(base_batch).to(device)},
86
+ [{"input_ids": torch.tensor(source_batch).to(device)}],
87
+ {"sources->base": ([[[pos_i]] * len(base_batch)],
88
+ [[[pos_i]] * len(base_batch)])}
89
+ )
90
+ distrib = embed_to_distrib(
91
+ model, counterfactual_outputs.last_hidden_state,
92
+ logits=False
93
+ )
94
+ for i in range(len(base_batch)):
95
+ for token in tokens:
96
+ data.append({
97
+ 'example': batch_i + i,
98
+ 'token': token,
99
+ 'prob': float(distrib[i][-1][token]),
100
+ 'layer': layer_i,
101
+ 'pos': pos_i,
102
+ 'type': "block_output"
103
+ })
104
+ return pd.DataFrame(data)
105
+
106
+
107
+ if __name__ == "__main__":
108
+
109
+ parser = argparse.ArgumentParser(
110
+ prog='Run intervention tests for subject-verb agreement on hop models',
111
+ description='Run interventions for subject-verb agreement on hop models')
112
+ parser.add_argument('perturbation_type',
113
+ default='all',
114
+ const='all',
115
+ nargs='?',
116
+ choices=PERTURBATIONS.keys(),
117
+ help='Perturbation function used to transform BabyLM dataset')
118
+ parser.add_argument('train_set',
119
+ default='all',
120
+ const='all',
121
+ nargs='?',
122
+ choices=["100M", "10M"],
123
+ help='BabyLM train set')
124
+ parser.add_argument('random_seed', type=int, help="Random seed")
125
+ parser.add_argument('paren_model',
126
+ default='all',
127
+ const='all',
128
+ nargs='?',
129
+ choices=list(PAREN_MODELS.keys()) + ["randinit"],
130
+ help='Parenthesis model')
131
+ parser.add_argument('-np', '--no_pos_encodings', action='store_true',
132
+ help="Train GPT-2 with no positional encodings")
133
+
134
+ # Get args
135
+ args = parser.parse_args()
136
+
137
+ if "hop" not in args.perturbation_type:
138
+ raise Exception(
139
+ "'{args.perturbation_type}' is not a valid hop perturbation")
140
+
141
+ # Get examples to run interventions
142
+ data_df = pd.read_csv("hop_agreement_data.csv")
143
+ bases = [[int(tok) for tok in seq.split()]
144
+ for seq in list(data_df["Singular"])]
145
+ sources = [[int(tok) for tok in seq.split()]
146
+ for seq in list(data_df["Plural"])]
147
+
148
+ # Only get first three tokens of each example for control model
149
+ if args.perturbation_type == "hop_control":
150
+ bases = [row[:3] for row in bases]
151
+ sources = [row[:3] for row in sources]
152
+
153
+ # Get model and run intervention experiments
154
+ device = "cuda"
155
+ result_df = None
156
+ for ckpt in CHECKPOINTS:
157
+ print(f"Checkpoint: {ckpt}")
158
+ model = get_model(args.perturbation_type, args.train_set,
159
+ args.random_seed, args.paren_model, ckpt,
160
+ args.no_pos_encodings)
161
+ if result_df is None:
162
+ result_df = run_interventions(model, bases, sources)
163
+ result_df["ckpt"] = ckpt
164
+ else:
165
+ ckpt_df = run_interventions(model, bases, sources)
166
+ ckpt_df["ckpt"] = ckpt
167
+ result_df = pd.concat((result_df, ckpt_df), axis=0)
168
+
169
+ # Create directory for results
170
+ nps = '_no_pos_encodings' if args.no_pos_encodings else ''
171
+ result_directory = f"hop_intervention_results/{args.perturbation_type}_{args.train_set}{nps}/"
172
+ if not os.path.exists(result_directory):
173
+ os.makedirs(result_directory)
174
+
175
+ # Write results
176
+ result_df.to_csv(result_directory + f"{args.paren_model}_seed{args.random_seed}.csv", index=False)
hop_surprisal/hop_surprisal.ipynb ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "# For importing utils\n",
10
+ "import sys\n",
11
+ "sys.path.append(\"..\")"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": 2,
17
+ "metadata": {},
18
+ "outputs": [
19
+ {
20
+ "name": "stderr",
21
+ "output_type": "stream",
22
+ "text": [
23
+ "/nlp/scr/kallini/miniconda3/envs/llmenv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
24
+ " from .autonotebook import tqdm as notebook_tqdm\n"
25
+ ]
26
+ }
27
+ ],
28
+ "source": [
29
+ "import numpy as np\n",
30
+ "import pandas as pd\n",
31
+ "import matplotlib.pyplot as plt\n",
32
+ "from matplotlib.patches import Patch\n",
33
+ "from scipy import stats\n",
34
+ "from utils import PERTURBATIONS"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": 3,
40
+ "metadata": {},
41
+ "outputs": [],
42
+ "source": [
43
+ "def get_surprisal_differences(perturbation, seed, ckpt, pos_encodings=True):\n",
44
+ "\n",
45
+ " # Load surprisal DataFrame\n",
46
+ " surprisals_path = \"hop_surprisal_results/{}_100M{}/randinit_seed{}.csv\"\n",
47
+ " nps = \"\" if pos_encodings else \"_no_positional_encodings\"\n",
48
+ " surprisal_df = pd.read_csv(surprisals_path.format(perturbation, nps, seed))\n",
49
+ " \n",
50
+ " # Get summary stats for suprisal differences\n",
51
+ " marker_token_surprisals = surprisal_df[f\"Marker Token Surprisals (ckpt {ckpt})\"]\n",
52
+ " nomarker_token_surprisals = surprisal_df[f\"No Marker Token Surprisals (ckpt {ckpt})\"]\n",
53
+ " differences = nomarker_token_surprisals - marker_token_surprisals\n",
54
+ " avg_differences = differences.mean()\n",
55
+ "\n",
56
+ " return avg_differences\n",
57
+ "\n",
58
+ "def get_summary_stats(l):\n",
59
+ " # Calculate confidence interval using t-distribution\n",
60
+ " mean = np.mean(l)\n",
61
+ " sem = stats.sem(l)\n",
62
+ " ci_lower, ci_upper = stats.t.interval(0.95, df=len(l)-1, loc=mean, scale=sem)\n",
63
+ " return mean, (ci_upper - ci_lower) / 2\n",
64
+ " "
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "code",
69
+ "execution_count": 4,
70
+ "metadata": {},
71
+ "outputs": [],
72
+ "source": [
73
+ "def plot_surprisal_differences(ax, seeds, ckpt, colors, hatches, pos_encodings):\n",
74
+ "\n",
75
+ " hop_control_seeds = []\n",
76
+ " hop_tokens4_seeds = []\n",
77
+ " hop_words4_seeds = []\n",
78
+ " for seed in seeds:\n",
79
+ " # Get summary stats for each hop model\n",
80
+ " avg_hop_control = get_surprisal_differences(\"hop_control\", seed, ckpt, pos_encodings)\n",
81
+ " avg_hop_tokens4 = get_surprisal_differences(\"hop_tokens4\", seed, ckpt, pos_encodings)\n",
82
+ " avg_hop_words4 = get_surprisal_differences(\"hop_words4\", seed, ckpt, pos_encodings)\n",
83
+ "\n",
84
+ " # Append results\n",
85
+ " hop_control_seeds.append(avg_hop_control)\n",
86
+ " hop_tokens4_seeds.append(avg_hop_tokens4)\n",
87
+ " hop_words4_seeds.append(avg_hop_words4)\n",
88
+ "\n",
89
+ " if len(seeds) > 1:\n",
90
+ " # Prepare data to plot\n",
91
+ " summary_stats = [\n",
92
+ " get_summary_stats(hop_control_seeds),\n",
93
+ " get_summary_stats(hop_tokens4_seeds),\n",
94
+ " get_summary_stats(hop_words4_seeds)\n",
95
+ " ]\n",
96
+ " else:\n",
97
+ " summary_stats = [\n",
98
+ " (hop_control_seeds[0], 0),\n",
99
+ " (hop_tokens4_seeds[0], 0),\n",
100
+ " (hop_words4_seeds[0], 0),\n",
101
+ " ]\n",
102
+ "\n",
103
+ " x = np.arange(3) # label locations\n",
104
+ " width = 0.8 # width of the bars\n",
105
+ "\n",
106
+ " # Iterate over pos / no pos groups\n",
107
+ " for i, (avg, err) in enumerate(summary_stats):\n",
108
+ " # Iterate over models and plot bars\n",
109
+ " color = colors[i]\n",
110
+ " hatch = hatches[i]\n",
111
+ " ax.bar(x[i], avg, width, yerr=err, label=None,\n",
112
+ " color=color, hatch=hatch, edgecolor=\"w\", zorder=2)\n",
113
+ "\n",
114
+ " ax.set_xticks([])\n",
115
+ " ax.grid(zorder=0, color=\"lightgray\")\n",
116
+ " ax.set_title(f\"{ckpt} Steps\")\n",
117
+ "\n",
118
+ "\n",
119
+ "def plot_surprisal_differences_checkpoints(seeds, checkpoints, pos_encodings=True):\n",
120
+ "\n",
121
+ " # Colors patterns for bars\n",
122
+ " color1=PERTURBATIONS[\"hop_control\"][\"color\"]\n",
123
+ " color2=PERTURBATIONS[\"hop_tokens4\"][\"color\"]\n",
124
+ " color3=PERTURBATIONS[\"hop_words4\"][\"color\"]\n",
125
+ " colors = [color1, color2, color3]\n",
126
+ "\n",
127
+ " hatch1 = ''\n",
128
+ " hatch2 = '///'\n",
129
+ " hatch3 = '..'\n",
130
+ " hatches = [hatch1, hatch2, hatch3]\n",
131
+ "\n",
132
+ " # Create a figure with multiple subplots\n",
133
+ " fig, axs = plt.subplots(2, 3, figsize=(6, 4), sharey=True)\n",
134
+ " axes_flat = axs.flatten()\n",
135
+ "\n",
136
+ " # Call individual plot function with different parameters for each subplot\n",
137
+ " for i, checkpoint in enumerate(checkpoints):\n",
138
+ " plot_surprisal_differences(\n",
139
+ " axes_flat[i], seeds, checkpoint, colors, hatches, pos_encodings)\n",
140
+ "\n",
141
+ " legend_elements = [Patch(facecolor=color1, hatch=hatch1,\n",
142
+ " edgecolor=\"w\", label='NoHop'),\n",
143
+ " Patch(facecolor=color2, hatch=hatch2,\n",
144
+ " edgecolor=\"w\", label='TokenHop'),\n",
145
+ " Patch(facecolor=color3, hatch=hatch3,\n",
146
+ " edgecolor=\"w\", label='WordHop')]\n",
147
+ " fig.legend(handles=legend_elements, ncol=3, loc=\"center\",\n",
148
+ " bbox_to_anchor=(0.55, 0), frameon=False)\n",
149
+ " \n",
150
+ " fig.supylabel(\"Surprisal Difference\", fontsize=12, x=0.04)\n",
151
+ "\n",
152
+ " # Adjust layout and show plot\n",
153
+ " plt.tight_layout()"
154
+ ]
155
+ },
156
+ {
157
+ "cell_type": "code",
158
+ "execution_count": 5,
159
+ "metadata": {},
160
+ "outputs": [
161
+ {
162
+ "data": {
163
+ "image/png": "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",
164
+ "text/plain": [
165
+ "<Figure size 600x400 with 6 Axes>"
166
+ ]
167
+ },
168
+ "metadata": {},
169
+ "output_type": "display_data"
170
+ }
171
+ ],
172
+ "source": [
173
+ "CHECKPOINTS = [300, 600, 900, 1200, 1500, 3000]\n",
174
+ "SEEDS = [0, 14, 41, 53, 96]\n",
175
+ "\n",
176
+ "plot_surprisal_differences_checkpoints(seeds=SEEDS, checkpoints=CHECKPOINTS)\n",
177
+ "plt.savefig(f\"figures/hop_surprisals.pdf\", format=\"pdf\", bbox_inches=\"tight\")"
178
+ ]
179
+ },
180
+ {
181
+ "cell_type": "code",
182
+ "execution_count": 6,
183
+ "metadata": {},
184
+ "outputs": [
185
+ {
186
+ "data": {
187
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkUAAAGhCAYAAABvQ8DIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB+I0lEQVR4nO3dd3wT9f8H8FeS7t0CXdACHZQhoyB7b8qQjYACIgoiIspPEJRREARBEFGm8mUo8mUpfBFB9pKNILIpe5RNNxSafH5/xEZCRpNrSi/J6/l45NHm7nP3+Vz7TvvK3eVOIYQQICIiInJyysIeABEREZEcMBQRERERgaGIiIiICABDEREREREAhiIiIiIiAAxFRERERAAYioiIiIgAMBQRERERAWAoIiIiIgLAUEREREQEgKFIVk6ePImuXbsiKioKXl5eKFq0KBo0aIB169YZbX/69Gm0atUKPj4+CAoKQq9evXD37l2DdhqNBlOmTEHp0qXh4eGBSpUqYdmyZRaPa8+ePUhISEDx4sXh4eGByMhItGvXDj/99JOuTVZWFhITE7Fjxw6rt5scz59//olXXnkFQUFB8PLywksvvYSZM2catNu7dy/q1asHLy8vhIaG4v3330dGRoZBu+zsbHz88ccIDw+Hp6cnatasic2bN1s8nnXr1qFhw4YIDg6Gl5cXoqKi0K1bN2zcuFHX5ubNm0hMTMSxY8ckbTM5hiNHjqBVq1bw8/ODr68vWrRoYbImWL8OSJBsrF+/XrRs2VIkJiaK+fPnixkzZoj69esLAGLevHl6ba9duyaKFi0qoqOjxddffy0mTpwoAgMDReXKlUV2drZe2xEjRggA4u233xbz588Xbdq0EQDEsmXL8hzTihUrhEKhEPHx8eKLL74Q8+fPFyNHjhR169YVjRo10rW7e/euACDGjh1rk58F2a/ff/9duLm5iZo1a4rp06eL+fPni48//lgMGzZMr93Ro0eFh4eHiI+PF3PmzBGffvqpcHd3F61atTJYZ/fu3YWLi4v46KOPxLx580Tt2rWFi4uL2L17d57jmTp1qgAgGjZsKKZPny7mzp0rPvroI1GlShXRp08fXbtDhw4JAGLhwoX5/RGQnTpy5Ijw8PAQsbGx4ssvvxRTpkwRpUqVEn5+fuLMmTN6bVm/jomhSOZycnJE5cqVRVxcnN70gQMHCk9PT3HlyhXdtM2bNxsEqOvXrwtXV1cxaNAg3TSNRiPq168vSpQoIXJycsz2X758eVGhQgWDoCWEELdv39Z9z1BEQgiRmpoqQkJCRMeOHYVarTbbNiEhQYSFhYnU1FTdtO+++04AEL///rtu2oEDBwQAMXXqVN20R48eiejoaFG7dm2zfTx9+lT4+fmJ5s2bG53/bA3znwq1bt1aBAYGinv37umm3bx5U/j4+IhOnTrptWX9OiaGIjvQtm1bERISojctODhYdO3a1aBtmTJlRNOmTXXPZ82aJQCIkydP6rX76aefBIA836m4u7uLN954w2ybS5cuCQAGj2cD0unTp0Xnzp1FYGCgcHd3F9WqVRNr167VW8/ChQsFALFz507Rv39/ERQUJHx9fUWvXr3EgwcP9NoeOnRItGjRQhQpUkR4eHiIUqVKib59+5odJxW8OXPmCADi1KlTQgghMjIyjIaj1NRU4eLiYrD3KDs7W/j4+Ih+/frppg0bNkyoVCq9fz5CCPH5558LAOLq1asmx5OcnCwAiMTERLPj3r59u9EafvYfzP79+0XLli2Fn5+f8PT0FA0aNBB79uzRW8/YsWMFAHH69GnRtWtX4evrK4KCgsT7778vHj16pNd206ZNom7dusLf3194e3uLMmXKiJEjR5odJxUsX19fo39X27RpI9zc3ER6eroQgvUrhOPWL88pkqHMzEzcu3cPFy5cwFdffYUNGzagadOmuvk3btzAnTt38PLLLxssW6NGDRw9elT3/OjRo/D29ka5cuUM2uXON6dkyZLYunUrrl+/brJNsWLFMGfOHABAx44d8cMPP+CHH35Ap06dAGjPlapVqxZOnz6NESNGYNq0afD29kaHDh3wyy+/GKzvvffew+nTp5GYmIjevXtj6dKl6NChA4QQAIA7d+6gRYsWuHz5MkaMGIFvvvkGr732Gvbv3292W6jgbdmyBX5+frhx4wbi4uLg4+MDPz8/DBw4EI8fP9a1+/vvv5GTk2NQw25ubqhSpYpBDZcpUwZ+fn56bXNr2Nw5FMHBwfD09MS6devw4MEDk+3KlSuH8ePHAwD69++vq+EGDRoAALZt24YGDRogLS0NY8eOxeeff46UlBQ0adIEBw8eNFhft27d8PjxY0yaNAmtW7fGzJkz0b9/f938kydPom3btsjOzsb48eMxbdo0vPLKK/jj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NLB4T1GRIkUQFRUleVBERERELxpvCEtEREQE3hCWiIiICABDEREREREAhiIiIiIiAAxFRERERAAYigpNRkYGxo4di1atWiEoKAgKhQKLFi0yaKfRaLBo0SK88soriIiIgLe3N1566SVMmDABjx8/NrruBQsWoFy5cvDw8EBsbCy++eYbo+1u3LiBbt26ISAgAH5+fmjfvj0uXrxo0fifPHmCr7/+GvHx8fDz80NAQAAqVKiA/v3748yZM7p2e/fuRWJiIlJSUixaL9kHS+sXAN544w0oFAqDR9myZQ3aajQaTJkyBaVLl4aHhwcqVaqEZcuWGV3v6dOn0apVK/j4+CAoKAi9evXC3bt3rRr/Sy+9BG9vbxQpUgRVqlTBkCFDcPPmTV273377DYmJiRatk+zLyZMn0bVrV0RFRcHLywtFixZFgwYNsG7dOqPtLa031rCdE1QoLl26JACIyMhI0ahRIwFALFy40KBdenq6ACBq1aolJkyYIObPny/69u0rlEqlaNSokdBoNHrt586dKwCIzp07i/nz54tevXoJAGLy5MkG642NjRXBwcHiiy++ENOnTxcRERGiRIkS4t69e3mOv23btkKlUonXX39dzJo1S8yYMUO88847okSJEnrbMXXqVAFAXLp0ScqPiWTK0voVQog+ffoId3d38cMPP+g9/ve//xm0HTFihAAg3n77bTF//nzRpk0bAUAsW7ZMr921a9dE0aJFRXR0tPj666/FxIkTRWBgoKhcubLIzs42O/YnT56I+Ph44enpKd555x0xd+5c8eWXX4q+ffuKokWLiu3bt+vaDho0SPDPpGNav369aNmypUhMTBTz588XM2bMEPXr1xcAxLx58/TaWlNvrGH7xp9UIXn8+LFITk4WQghx6NAhk/9UsrOzxR9//GEwfdy4cQKA2Lx5s25aVlaWKFKkiGjTpo1e29dee014e3uLBw8e6KZ98cUXAoA4ePCgbtrp06eFSqUSI0eONDv2gwcPCgBi4sSJBvNycnL0QhVDkWOytH6F0IYib2/vPNd5/fp14erqKgYNGqSbptFoRP369UWJEiVETk6ObvrAgQOFp6enuHLlim7a5s2bjf5De96KFSsEALF06VKDeY8ePRKpqam65/yH4lxycnJE5cqVRVxcnN50S+uNNWz/LPpJlSpVSpQuXdqqR1RUVEGP3WHk9U/FmOPHjwsAYubMmbpp69evFwDE+vXr9dru3btXABA//PCDblr16tVF9erVDdbbokULER0dbbbvZcuWCQBix44dZtuNHTtWADB4PBuQfvjhB1G1alXh4eEhAgMDxauvviquXr2qt56GDRuKChUqiMOHD4vatWsLDw8PUapUKTFnzhyDPmfOnCnKly8vPD09RUBAgKhWrZrRPxxkO5aGopycHL0/1s+bNWuWACBOnjypN/2nn34SAMTu3bt104KDg0XXrl0N1lGmTBnRtGlTs+OdNGmSACAuX75stl2fPn2M1m8utVotvvrqK1G+fHnh7u4ugoODRf/+/fXefAghRMmSJUWbNm3E77//LipXrizc3d1FuXLlxOrVq/XaPXnyRCQmJoqYmBjh7u4ugoKCRN26dcWmTZvMjpNsq23btiIkJERvmqX1xhq2/xq26IrWDRs25H3PZObWrVsAgKJFi+qmHT16FADw8ssv67WtVq0alEoljh49itdffx0ajQbHjx/Hm2++abDeGjVqYNOmTUhPT4evr6/RvkuWLAkAWLp0KerWrQsXF+Nl1KlTJ5w7dw7Lli3DV199pRtrsWLFAAATJ07E6NGj0a1bN7z11lu4e/cuvvnmGzRo0ABHjx5FQECAbl0PHz5E69at0a1bN/To0QMrVqzAwIED4ebmptuO7777Du+//z66dOmCIUOG4PHjxzh+/DgOHDiAnj175vkzpYKTlZUFPz8/ZGVlITAwED169MAXX3wBHx8fXZujR4/C29sb5cqV01u2Ro0auvn16tXDjRs3cOfOHYM6z23722+/mR1Lbv0uWbIEo0aNMvm3bcCAAbh58yY2b96MH374wej8RYsWoW/fvnj//fdx6dIlfPvttzh69Cj++OMPuLq66tqeP38er776Kt555x306dMHCxcuRNeuXbFx40Y0b94cAJCYmIhJkybhrbfeQo0aNZCWlobDhw/jzz//1LUh28vMzMSjR4+QmpqK//3vf9iwYQNeffVV3Xxr6o017AA1XNipjKTtKWrWrJnw8/MTDx8+1E0bNGiQUKlURtsXK1ZMdO/eXQghxN27dwUAMX78eIN2ue90zpw5Y7JvjUYjGjZsKACIkJAQ0aNHDzFr1iy93cC5TB0+u3z5slCpVAaH4P7++2/h4uKiNz23r2nTpummZWdniypVqojg4GDx5MkT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",
188
+ "text/plain": [
189
+ "<Figure size 600x400 with 6 Axes>"
190
+ ]
191
+ },
192
+ "metadata": {},
193
+ "output_type": "display_data"
194
+ }
195
+ ],
196
+ "source": [
197
+ "plot_surprisal_differences_checkpoints(seeds=[53], checkpoints=CHECKPOINTS, pos_encodings=False)\n",
198
+ "plt.savefig(f\"figures/hop_surprisals_no_pos_encodings.pdf\", format=\"pdf\", bbox_inches=\"tight\")"
199
+ ]
200
+ }
201
+ ],
202
+ "metadata": {
203
+ "kernelspec": {
204
+ "display_name": "babyenv",
205
+ "language": "python",
206
+ "name": "python3"
207
+ },
208
+ "language_info": {
209
+ "codemirror_mode": {
210
+ "name": "ipython",
211
+ "version": 3
212
+ },
213
+ "file_extension": ".py",
214
+ "mimetype": "text/x-python",
215
+ "name": "python",
216
+ "nbconvert_exporter": "python",
217
+ "pygments_lexer": "ipython3",
218
+ "version": "3.10.11"
219
+ }
220
+ },
221
+ "nbformat": 4,
222
+ "nbformat_minor": 2
223
+ }
hop_surprisal/hop_surprisal.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # hop_surprisal.py
2
+ # Author: Julie Kallini
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ import os
9
+ import torch
10
+ import pandas as pd
11
+ import tqdm
12
+ import argparse
13
+ from numpy.random import default_rng
14
+ from transformers import GPT2LMHeadModel
15
+ from gpt2_no_positional_encoding_model import GPT2NoPositionalEncodingLMHeadModel
16
+ from itertools import zip_longest
17
+ from glob import glob
18
+ from utils import CHECKPOINT_READ_PATH, PERTURBATIONS, PAREN_MODELS, \
19
+ BABYLM_DATA_PATH, gpt2_hop_tokenizer, \
20
+ marker_sg_token, marker_pl_token, compute_surprisals
21
+
22
+
23
+ MAX_TRAINING_STEPS = 3000
24
+ CHECKPOINTS = list(range(100, MAX_TRAINING_STEPS+1, 100))
25
+
26
+ if __name__ == "__main__":
27
+
28
+ parser = argparse.ArgumentParser(
29
+ prog='Get marker token surprisals for hop verb perturbations',
30
+ description='Marker token surprisals')
31
+ parser.add_argument('perturbation_type',
32
+ default='all',
33
+ const='all',
34
+ nargs='?',
35
+ choices=PERTURBATIONS.keys(),
36
+ help='Perturbation function used to transform BabyLM dataset')
37
+ parser.add_argument('train_set',
38
+ default='all',
39
+ const='all',
40
+ nargs='?',
41
+ choices=["100M", "10M"],
42
+ help='BabyLM train set')
43
+ parser.add_argument('random_seed', type=int, help="Random seed")
44
+ parser.add_argument('paren_model',
45
+ default='all',
46
+ const='all',
47
+ nargs='?',
48
+ choices=list(PAREN_MODELS.keys()) + ["randinit"],
49
+ help='Parenthesis model')
50
+ parser.add_argument('-np', '--no_pos_encodings', action='store_true',
51
+ help="Train GPT-2 with no positional encodings")
52
+
53
+ # Get args
54
+ args = parser.parse_args()
55
+ no_pos_encodings_underscore = "_no_positional_encodings" if args.no_pos_encodings else ""
56
+
57
+ if "hop" not in args.perturbation_type:
58
+ raise Exception(
59
+ "'{args.perturbation_type}' is not a valid hop perturbation")
60
+
61
+ # Get path to model
62
+ model = f"babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}_seed{args.random_seed}"
63
+ model_path = f"{CHECKPOINT_READ_PATH}/babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}/{model}/runs/{model}/checkpoint-"
64
+
65
+ # Get perturbed test files
66
+ test_files = sorted(glob(BABYLM_DATA_PATH +
67
+ "/babylm_data_perturbed/babylm_{}/babylm_test_affected/*".format(args.perturbation_type)))
68
+
69
+ EOS_TOKEN = gpt2_hop_tokenizer.eos_token_id
70
+ FILE_SAMPLE_SIZE = 1000
71
+ MAX_SEQ_LEN = 1024
72
+ rng = default_rng(args.random_seed)
73
+
74
+ marker_token_sequences = []
75
+ nomarker_token_sequences = []
76
+ target_indices = []
77
+
78
+ # Iterate over data files to get surprisal data
79
+ print("Sampling BabyLM affected test files to extract surprisals...")
80
+ for test_file in test_files:
81
+ print(test_file)
82
+
83
+ # Get tokens from test file (+ eos token), and subsample
84
+ f = open(test_file, 'r')
85
+ file_token_sequences = [
86
+ [int(s) for s in l.split()] + [EOS_TOKEN] for l in f.readlines()]
87
+ file_token_sequences = [
88
+ toks for toks in file_token_sequences if len(toks) < MAX_SEQ_LEN]
89
+ sample_indices = rng.choice(
90
+ list(range(len(file_token_sequences))), FILE_SAMPLE_SIZE, replace=False)
91
+ file_token_sequences = [file_token_sequences[i]
92
+ for i in sample_indices]
93
+
94
+ file_target_indices = []
95
+ file_nomarker_token_sequences = []
96
+ for tokens in file_token_sequences:
97
+ # Find index of first marker token for surprisal target
98
+ target_index = None
99
+ for idx in range(len(tokens)):
100
+ if tokens[idx] in (marker_sg_token, marker_pl_token):
101
+ target_index = idx
102
+ break
103
+ assert (target_index is not None)
104
+
105
+ # Make a version of tokens with marker removed at surprisal target
106
+ nomarker_tokens = tokens.copy()
107
+ nomarker_tokens.pop(target_index)
108
+ assert (tokens[:target_index] == nomarker_tokens[:target_index])
109
+ assert (tokens[target_index] in (marker_sg_token, marker_pl_token))
110
+ assert (tokens[target_index+1] == nomarker_tokens[target_index])
111
+
112
+ file_target_indices.append(target_index)
113
+ file_nomarker_token_sequences.append(nomarker_tokens)
114
+
115
+ marker_token_sequences.extend(file_token_sequences)
116
+ nomarker_token_sequences.extend(file_nomarker_token_sequences)
117
+ target_indices.extend(file_target_indices)
118
+
119
+ # For logging/debugging, include decoded sentence
120
+ marker_sents = [gpt2_hop_tokenizer.decode(
121
+ toks) for toks in marker_token_sequences]
122
+ nomarker_sents = [gpt2_hop_tokenizer.decode(
123
+ toks) for toks in nomarker_token_sequences]
124
+
125
+ surprisal_df = pd.DataFrame({
126
+ "Sentences with Marker": marker_sents,
127
+ "Sentences without Marker": nomarker_sents,
128
+ })
129
+
130
+ BATCH_SIZE = 32
131
+ device = "cuda"
132
+ for ckpt in CHECKPOINTS:
133
+ print(f"Checkpoint: {ckpt}")
134
+
135
+ # Load model
136
+ if args.no_pos_encodings:
137
+ model = GPT2NoPositionalEncodingLMHeadModel.from_pretrained(
138
+ model_path + str(ckpt)).to(device)
139
+ else:
140
+ model = GPT2LMHeadModel.from_pretrained(
141
+ model_path + str(ckpt)).to(device)
142
+
143
+ # Init lists for tracking correct/wrong surprisals for each example
144
+ marker_token_surprisals = []
145
+ nomarker_token_surprisals = []
146
+
147
+ # Iterate over data in batches
148
+ for i in tqdm.tqdm(range(0, len(marker_token_sequences), BATCH_SIZE)):
149
+
150
+ # Extract data for batch and pad the sequences
151
+ marker_batch = marker_token_sequences[i:i+BATCH_SIZE]
152
+ correct_batch_padded = zip(
153
+ *zip_longest(*marker_batch, fillvalue=gpt2_hop_tokenizer.eos_token_id))
154
+ marker_batch_tensors = torch.tensor(
155
+ list(correct_batch_padded)).to(device)
156
+
157
+ # Do the same for wrong batch
158
+ nomarker_batch = nomarker_token_sequences[i:i+BATCH_SIZE]
159
+ nomarker_batch_padded = zip(
160
+ *zip_longest(*nomarker_batch, fillvalue=gpt2_hop_tokenizer.eos_token_id))
161
+ nomarker_batch_tensors = torch.tensor(
162
+ list(nomarker_batch_padded)).to(device)
163
+
164
+ # Get target indices in a batch
165
+ targets_batch = target_indices[i:i+BATCH_SIZE]
166
+
167
+ # Compute marker/nomarker surprisals for batches
168
+ marker_surprisal_sequences = compute_surprisals(
169
+ model, marker_batch_tensors)
170
+ nomarker_surprisal_sequences = compute_surprisals(
171
+ model, nomarker_batch_tensors)
172
+
173
+ # Extract surprisals for target token
174
+ for marker_seq, nomarker_seq, idx in \
175
+ zip(marker_surprisal_sequences, nomarker_surprisal_sequences, targets_batch):
176
+ marker_token_surprisals.append(marker_seq[idx])
177
+ nomarker_token_surprisals.append(nomarker_seq[idx])
178
+
179
+ # Add surprisals to df
180
+ ckpt_df = pd.DataFrame(
181
+ list(zip(marker_token_surprisals, nomarker_token_surprisals)),
182
+ columns=[f'Marker Token Surprisals (ckpt {ckpt})',
183
+ f'No Marker Token Surprisals (ckpt {ckpt})']
184
+ )
185
+ surprisal_df = pd.concat((surprisal_df, ckpt_df), axis=1)
186
+
187
+ # Write results to CSV
188
+ directory = f"hop_surprisal_results/{args.perturbation_type}_{args.train_set}{no_pos_encodings_underscore}"
189
+ if not os.path.exists(directory):
190
+ os.makedirs(directory)
191
+ file = directory + f"/{args.paren_model}_seed{args.random_seed}.csv"
192
+ print(f"Writing results to CSV: {file}")
193
+ surprisal_df.to_csv(file)
hop_surprisal/hop_surprisal.sh ADDED
File without changes
hop_surprisal/hop_surprisal_gpt2.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # hop_surprisal.py
2
+ # Author: Yaning
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ import os
9
+ import torch
10
+ import pandas as pd
11
+ import tqdm
12
+ import argparse
13
+ from numpy.random import default_rng
14
+
15
+ from itertools import zip_longest
16
+ from glob import glob
17
+ from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling
18
+ from utils_gpt2 import CHECKPOINT_READ_PATH, PERTURBATIONS, PAREN_MODELS, \
19
+ BABYLM_DATA_PATH, gpt2_hop_tokenizer, \
20
+ marker_sg_token, marker_pl_token, compute_surprisals
21
+
22
+
23
+ MODEL_NAME = "GPT2-Hop"
24
+
25
+ device = "cuda" if torch.cuda.is_available() else "cpu"
26
+ MAX_TRAINING_STEPS = 1500
27
+ # CHECKPOINTS = list(range(500, MAX_TRAINING_STEPS+1, 500))
28
+ CHECKPOINTS = [2241] # control 2241
29
+
30
+ if __name__ == "__main__":
31
+
32
+ parser = argparse.ArgumentParser(
33
+ prog='Get marker token surprisals for hop verb perturbations',
34
+ description='Marker token surprisals')
35
+ parser.add_argument('perturbation_type',
36
+ default='all',
37
+ const='all',
38
+ nargs='?',
39
+ choices=PERTURBATIONS.keys(),
40
+ help='Perturbation function used to transform BabyLM dataset')
41
+ parser.add_argument('train_set',
42
+ default='all',
43
+ const='all',
44
+ nargs='?',
45
+ choices=["100M", "10M"],
46
+ help='BabyLM train set')
47
+
48
+ parser.add_argument('random_seed', type=int, help="Random seed")
49
+
50
+ # Get args
51
+ args = parser.parse_args()
52
+
53
+ if "hop" not in args.perturbation_type:
54
+ raise Exception(
55
+ "'{args.perturbation_type}' is not a valid hop perturbation")
56
+
57
+ # Get path to model
58
+ # model = f"babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}_seed{args.random_seed}"
59
+ # model_path = f"{CHECKPOINT_READ_PATH}/babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}/{model}/runs/{model}/checkpoint-"
60
+
61
+ model_path = f'../train/checkpoints/{MODEL_NAME}/babylm_{args.perturbation_type}_10M_seed0/runs/checkpoint-'
62
+
63
+ # Get perturbed test files
64
+ # test_files = sorted(glob(BABYLM_DATA_PATH +
65
+ # "/babylm_data_perturbed/babylm_{}/babylm_test_affected/*".format(args.perturbation_type)))
66
+ test_files = sorted(glob("../data/Perturbed_data/gpt2/babylm_{}/babylm_test_affected/*".format(args.perturbation_type)))
67
+ print("test_files:", test_files)
68
+
69
+ EOS_TOKEN = gpt2_hop_tokenizer.eos_token_id
70
+ print("EOS_TOKEN:", EOS_TOKEN)
71
+ FILE_SAMPLE_SIZE = 500
72
+ MAX_SEQ_LEN = 512
73
+ rng = default_rng(args.random_seed)
74
+
75
+ marker_token_sequences = []
76
+ nomarker_token_sequences = []
77
+ target_indices = []
78
+
79
+ # Iterate over data files to get surprisal data
80
+ print("Sampling BabyLM affected test files to extract surprisals...")
81
+ for test_file in test_files:
82
+ print(test_file)
83
+
84
+ # Get tokens from test file (+ eos token), and subsample
85
+ f = open(test_file, 'r')
86
+ file_token_sequences = [
87
+ [int(s) for s in l.split()] + [EOS_TOKEN] for l in f.readlines()]
88
+ file_token_sequences = [
89
+ toks for toks in file_token_sequences if len(toks) < MAX_SEQ_LEN]
90
+ sample_indices = rng.choice(
91
+ list(range(len(file_token_sequences))), FILE_SAMPLE_SIZE, replace=False)
92
+ file_token_sequences = [file_token_sequences[i]
93
+ for i in sample_indices]
94
+
95
+ file_target_indices = []
96
+ file_nomarker_token_sequences = []
97
+ for tokens in file_token_sequences:
98
+ # Find index of first marker token for surprisal target
99
+ target_index = None
100
+ for idx in range(len(tokens)):
101
+ # print("marker_sg_token", marker_sg_token)
102
+ # print("marker_pl_token", marker_pl_token)
103
+ if tokens[idx] in (marker_sg_token, marker_pl_token):
104
+ target_index = idx
105
+ break
106
+ assert (target_index is not None)
107
+
108
+ # Make a version of tokens with marker removed at surprisal target
109
+ nomarker_tokens = tokens.copy()
110
+ nomarker_tokens.pop(target_index)
111
+ assert (tokens[:target_index] == nomarker_tokens[:target_index])
112
+ assert (tokens[target_index] in (marker_sg_token, marker_pl_token))
113
+ assert (tokens[target_index+1] == nomarker_tokens[target_index])
114
+
115
+ file_target_indices.append(target_index)
116
+ file_nomarker_token_sequences.append(nomarker_tokens)
117
+
118
+ marker_token_sequences.extend(file_token_sequences)
119
+ nomarker_token_sequences.extend(file_nomarker_token_sequences)
120
+ target_indices.extend(file_target_indices)
121
+
122
+ # For logging/debugging, include decoded sentence
123
+ marker_sents = [gpt2_hop_tokenizer.decode(
124
+ toks) for toks in marker_token_sequences]
125
+ nomarker_sents = [gpt2_hop_tokenizer.decode(
126
+ toks) for toks in nomarker_token_sequences]
127
+
128
+ surprisal_df = pd.DataFrame({
129
+ "Sentences with Marker": marker_sents,
130
+ "Sentences without Marker": nomarker_sents,
131
+ })
132
+
133
+ # BATCH_SIZE = 32
134
+ BATCH_SIZE = 32
135
+ for ckpt in CHECKPOINTS:
136
+ print(f"Checkpoint: {ckpt}")
137
+
138
+ model = AutoModelForCausalLM.from_pretrained(model_path + str(ckpt)).to(device)
139
+ # model = AutoModelForCausalLM.from_pretrained(model_path + str(ckpt))
140
+ orig_vocab_size = model.config.vocab_size
141
+ model.resize_token_embeddings(len(gpt2_hop_tokenizer))
142
+ # Init lists for tracking correct/wrong surprisals for each example
143
+ marker_token_surprisals = []
144
+ nomarker_token_surprisals = []
145
+
146
+ # Iterate over data in batches
147
+ for i in tqdm.tqdm(range(0, len(marker_token_sequences), BATCH_SIZE)):
148
+
149
+ # Extract data for batch and pad the sequences
150
+ marker_batch = marker_token_sequences[i:i+BATCH_SIZE]
151
+ correct_batch_padded = zip(
152
+ *zip_longest(*marker_batch, fillvalue=gpt2_hop_tokenizer.eos_token_id))
153
+ # marker_batch_tensors = torch.tensor(
154
+ # list(correct_batch_padded))
155
+ marker_batch_tensors = torch.tensor(
156
+ list(correct_batch_padded)).to(device)
157
+
158
+ # Do the same for wrong batch
159
+ nomarker_batch = nomarker_token_sequences[i:i+BATCH_SIZE]
160
+ nomarker_batch_padded = zip(
161
+ *zip_longest(*nomarker_batch, fillvalue=gpt2_hop_tokenizer.eos_token_id))
162
+ # nomarker_batch_tensors = torch.tensor(
163
+ # list(nomarker_batch_padded))
164
+ nomarker_batch_tensors = torch.tensor(
165
+ list(nomarker_batch_padded)).to(device)
166
+
167
+ # Get target indices in a batch
168
+ targets_batch = target_indices[i:i+BATCH_SIZE]
169
+
170
+ # Compute marker/nomarker surprisals for batches
171
+ marker_surprisal_sequences = compute_surprisals(
172
+ model, marker_batch_tensors)
173
+ nomarker_surprisal_sequences = compute_surprisals(
174
+ model, nomarker_batch_tensors)
175
+
176
+ # Extract surprisals for target token
177
+ for marker_seq, nomarker_seq, idx in \
178
+ zip(marker_surprisal_sequences, nomarker_surprisal_sequences, targets_batch):
179
+ marker_token_surprisals.append(marker_seq[idx])
180
+ nomarker_token_surprisals.append(nomarker_seq[idx])
181
+
182
+ # Add surprisals to df
183
+ ckpt_df = pd.DataFrame(
184
+ list(zip(marker_token_surprisals, nomarker_token_surprisals)),
185
+ columns=[f'Marker Token Surprisals (ckpt {ckpt})',
186
+ f'No Marker Token Surprisals (ckpt {ckpt})']
187
+ )
188
+ surprisal_df = pd.concat((surprisal_df, ckpt_df), axis=1)
189
+
190
+ # Write results to CSV
191
+ directory = os.path.join(
192
+ "hop_surprisal_results",
193
+ MODEL_NAME,
194
+ f"{args.perturbation_type}_{args.train_set}"
195
+ )
196
+
197
+ os.makedirs(directory, exist_ok=True)
198
+
199
+ filename = f"{args.perturbation_type}_{args.train_set}_seed{args.random_seed}.csv"
200
+ file_path = os.path.join(directory, filename)
201
+
202
+ print(f"The file saves as to: {file_path}")
203
+ surprisal_df.to_csv(file_path, index=False)
204
+
hop_surprisal/hop_surprisal_llama3.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ wwwwwww# hop_surprisal.py
2
+ # Author: Yaning
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ import os
9
+ import torch
10
+ import pandas as pd
11
+ import tqdm
12
+ import argparse
13
+ from numpy.random import default_rng
14
+
15
+ from itertools import zip_longest
16
+ from glob import glob
17
+ from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling
18
+ from utils_llama import CHECKPOINT_READ_PATH, PERTURBATIONS, PAREN_MODELS, \
19
+ BABYLM_DATA_PATH, gpt2_hop_tokenizer, \
20
+ marker_sg_token, marker_pl_token, compute_surprisals
21
+
22
+
23
+ # MODEL_NAME = "GPT-2"
24
+ MODEL_NAME = "meta-llama/Llama-3.2-3B"
25
+
26
+ MAX_TRAINING_STEPS = 1500
27
+ # CHECKPOINTS = list(range(500, MAX_TRAINING_STEPS+1, 500))
28
+ CHECKPOINTS = [2376]
29
+
30
+ if __name__ == "__main__":
31
+
32
+ parser = argparse.ArgumentParser(
33
+ prog='Get marker token surprisals for hop verb perturbations',
34
+ description='Marker token surprisals')
35
+ parser.add_argument('perturbation_type',
36
+ default='all',
37
+ const='all',
38
+ nargs='?',
39
+ choices=PERTURBATIONS.keys(),
40
+ help='Perturbation function used to transform BabyLM dataset')
41
+ parser.add_argument('train_set',
42
+ default='all',
43
+ const='all',
44
+ nargs='?',
45
+ choices=["100M", "10M"],
46
+ help='BabyLM train set')
47
+
48
+ parser.add_argument('random_seed', type=int, help="Random seed")
49
+
50
+ # Get args
51
+ args = parser.parse_args()
52
+
53
+ if "hop" not in args.perturbation_type:
54
+ raise Exception(
55
+ "'{args.perturbation_type}' is not a valid hop perturbation")
56
+
57
+ # Get path to model
58
+ # model = f"babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}_seed{args.random_seed}"
59
+ # model_path = f"{CHECKPOINT_READ_PATH}/babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}/{model}/runs/{model}/checkpoint-"
60
+
61
+ model_path = f'../train/checkpoints/{MODEL_NAME}/babylm_{args.perturbation_type}_10M_seed0/runs/checkpoint-'
62
+
63
+ # Get perturbed test files
64
+ # test_files = sorted(glob(BABYLM_DATA_PATH +
65
+ # "/babylm_data_perturbed/babylm_{}/babylm_test_affected/*".format(args.perturbation_type)))
66
+ test_files = sorted(glob("../data/Perturbed_data/gpt2/babylm_{}/babylm_test_affected/*".format(args.perturbation_type)))
67
+ print("test_files:", test_files)
68
+
69
+ EOS_TOKEN = gpt2_hop_tokenizer.eos_token_id
70
+ print("EOS_TOKEN:", EOS_TOKEN)
71
+ FILE_SAMPLE_SIZE = 500
72
+ MAX_SEQ_LEN = 512
73
+ rng = default_rng(args.random_seed)
74
+
75
+ marker_token_sequences = []
76
+ nomarker_token_sequences = []
77
+ target_indices = []
78
+
79
+ # Iterate over data files to get surprisal data
80
+ print("Sampling BabyLM affected test files to extract surprisals...")
81
+ for test_file in test_files:
82
+ print(test_file)
83
+
84
+ # Get tokens from test file (+ eos token), and subsample
85
+ f = open(test_file, 'r')
86
+ file_token_sequences = [
87
+ [int(s) for s in l.split()] + [EOS_TOKEN] for l in f.readlines()]
88
+ file_token_sequences = [
89
+ toks for toks in file_token_sequences if len(toks) < MAX_SEQ_LEN]
90
+ sample_indices = rng.choice(
91
+ list(range(len(file_token_sequences))), FILE_SAMPLE_SIZE, replace=False)
92
+ file_token_sequences = [file_token_sequences[i]
93
+ for i in sample_indices]
94
+
95
+ file_target_indices = []
96
+ file_nomarker_token_sequences = []
97
+ for tokens in file_token_sequences:
98
+ # Find index of first marker token for surprisal target
99
+ target_index = None
100
+ for idx in range(len(tokens)):
101
+ # print("marker_sg_token", marker_sg_token)
102
+ # print("marker_pl_token", marker_pl_token)
103
+ if tokens[idx] in (marker_sg_token, marker_pl_token):
104
+ target_index = idx
105
+ break
106
+ assert (target_index is not None)
107
+
108
+ # Make a version of tokens with marker removed at surprisal target
109
+ nomarker_tokens = tokens.copy()
110
+ nomarker_tokens.pop(target_index)
111
+ assert (tokens[:target_index] == nomarker_tokens[:target_index])
112
+ assert (tokens[target_index] in (marker_sg_token, marker_pl_token))
113
+ assert (tokens[target_index+1] == nomarker_tokens[target_index])
114
+
115
+ file_target_indices.append(target_index)
116
+ file_nomarker_token_sequences.append(nomarker_tokens)
117
+
118
+ marker_token_sequences.extend(file_token_sequences)
119
+ nomarker_token_sequences.extend(file_nomarker_token_sequences)
120
+ target_indices.extend(file_target_indices)
121
+
122
+ # For logging/debugging, include decoded sentence
123
+ marker_sents = [gpt2_hop_tokenizer.decode(
124
+ toks) for toks in marker_token_sequences]
125
+ nomarker_sents = [gpt2_hop_tokenizer.decode(
126
+ toks) for toks in nomarker_token_sequences]
127
+
128
+ surprisal_df = pd.DataFrame({
129
+ "Sentences with Marker": marker_sents,
130
+ "Sentences without Marker": nomarker_sents,
131
+ })
132
+
133
+ # BATCH_SIZE = 32
134
+ BATCH_SIZE = 32
135
+ device = "cuda"
136
+ for ckpt in CHECKPOINTS:
137
+ print(f"Checkpoint: {ckpt}")
138
+
139
+ # model = AutoModelForCausalLM.from_pretrained(model_path + str(ckpt)).to(device)
140
+ model = AutoModelForCausalLM.from_pretrained(model_path + str(ckpt))
141
+ orig_vocab_size = model.config.vocab_size
142
+ model.resize_token_embeddings(len(gpt2_hop_tokenizer))
143
+ # Init lists for tracking correct/wrong surprisals for each example
144
+ marker_token_surprisals = []
145
+ nomarker_token_surprisals = []
146
+
147
+ # Iterate over data in batches
148
+ for i in tqdm.tqdm(range(0, len(marker_token_sequences), BATCH_SIZE)):
149
+
150
+ # Extract data for batch and pad the sequences
151
+ marker_batch = marker_token_sequences[i:i+BATCH_SIZE]
152
+ correct_batch_padded = zip(
153
+ *zip_longest(*marker_batch, fillvalue=gpt2_hop_tokenizer.eos_token_id))
154
+ marker_batch_tensors = torch.tensor(
155
+ list(correct_batch_padded))
156
+ # marker_batch_tensors = torch.tensor(
157
+ # list(correct_batch_padded)).to(device)
158
+
159
+ # Do the same for wrong batch
160
+ nomarker_batch = nomarker_token_sequences[i:i+BATCH_SIZE]
161
+ nomarker_batch_padded = zip(
162
+ *zip_longest(*nomarker_batch, fillvalue=gpt2_hop_tokenizer.eos_token_id))
163
+ nomarker_batch_tensors = torch.tensor(
164
+ list(nomarker_batch_padded))
165
+ # nomarker_batch_tensors = torch.tensor(
166
+ # list(nomarker_batch_padded)).to(device)
167
+
168
+ # Get target indices in a batch
169
+ targets_batch = target_indices[i:i+BATCH_SIZE]
170
+
171
+ # Compute marker/nomarker surprisals for batches
172
+ marker_surprisal_sequences = compute_surprisals(
173
+ model, marker_batch_tensors)
174
+ nomarker_surprisal_sequences = compute_surprisals(
175
+ model, nomarker_batch_tensors)
176
+
177
+ # Extract surprisals for target token
178
+ for marker_seq, nomarker_seq, idx in \
179
+ zip(marker_surprisal_sequences, nomarker_surprisal_sequences, targets_batch):
180
+ marker_token_surprisals.append(marker_seq[idx])
181
+ nomarker_token_surprisals.append(nomarker_seq[idx])
182
+
183
+ # Add surprisals to df
184
+ ckpt_df = pd.DataFrame(
185
+ list(zip(marker_token_surprisals, nomarker_token_surprisals)),
186
+ columns=[f'Marker Token Surprisals (ckpt {ckpt})',
187
+ f'No Marker Token Surprisals (ckpt {ckpt})']
188
+ )
189
+ surprisal_df = pd.concat((surprisal_df, ckpt_df), axis=1)
190
+
191
+ # Write results to CSV
192
+ directory = os.path.join(
193
+ "hop_surprisal_results",
194
+ MODEL_NAME,
195
+ f"{args.perturbation_type}_{args.train_set}"
196
+ )
197
+
198
+ os.makedirs(directory, exist_ok=True)
199
+
200
+ filename = f"{args.perturbation_type}_{args.train_set}_seed{args.random_seed}.csv"
201
+ file_path = os.path.join(directory, filename)
202
+
203
+ print(f"The file saves as to: {file_path}")
204
+ surprisal_df.to_csv(file_path, index=False)
205
+
hop_surprisal/hop_surprisal_results/GPT-2/hop_words4_10M/hop_words4_10M_seed0.csv ADDED
The diff for this file is too large to render. See raw diff
 
hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_control.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_control_10M_seed0.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_words4.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/0.5B/hop_words4_10M_seed0.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_control.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_control_10M_seed0.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_words4.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/1.5B/hop_words4_10M_seed0.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/3B/hop_control.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/3B/hop_words4.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/3B/hop_words4_10M_seed0.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/7B/hop_control.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/7B/hop_control_10M_seed0.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/7B/hop_words4.csv ADDED
File without changes
hop_surprisal/hop_surprisal_results/Qwen/7B/hop_words4_10M_seed0.csv ADDED
File without changes
impossible_llm.yaml ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: impossible_llm
2
+ channels:
3
+ - defaults
4
+ dependencies:
5
+ - _libgcc_mutex=0.1=main
6
+ - _openmp_mutex=5.1=1_gnu
7
+ - ca-certificates=2024.7.2=h06a4308_0
8
+ - ld_impl_linux-64=2.38=h1181459_1
9
+ - libffi=3.4.4=h6a678d5_1
10
+ - libgcc-ng=11.2.0=h1234567_1
11
+ - libgomp=11.2.0=h1234567_1
12
+ - libstdcxx-ng=11.2.0=h1234567_1
13
+ - ncurses=6.4=h6a678d5_0
14
+ - openssl=3.0.15=h5eee18b_0
15
+ - pip=24.2=py39h06a4308_0
16
+ - python=3.9.19=h955ad1f_1
17
+ - readline=8.2=h5eee18b_0
18
+ - setuptools=72.1.0=py39h06a4308_0
19
+ - sqlite=3.45.3=h5eee18b_0
20
+ - tk=8.6.14=h39e8969_0
21
+ - wheel=0.44.0=py39h06a4308_0
22
+ - xz=5.4.6=h5eee18b_1
23
+ - zlib=1.2.13=h5eee18b_1
24
+ - pip:
25
+ - accelerate==0.34.2
26
+ - aiohappyeyeballs==2.4.2
27
+ - aiohttp==3.10.6
28
+ - aiosignal==1.3.1
29
+ - asttokens==2.2.1
30
+ - async-timeout==4.0.3
31
+ - attrs==24.2.0
32
+ - backcall==0.2.0
33
+ - black==24.8.0
34
+ - blessed==1.20.0
35
+ - certifi==2023.11.17
36
+ - charset-normalizer==3.3.2
37
+ - click==8.1.7
38
+ - cmake==3.30.3
39
+ - comm==0.1.2
40
+ - contourpy==1.2.0
41
+ - cycler==0.12.1
42
+ - data==0.4
43
+ - datasets==3.0.1
44
+ - debugpy==1.6.7
45
+ - decorator==5.1.1
46
+ - dill==0.3.8
47
+ - emoji==2.8.0
48
+ - exceptiongroup==1.1.0
49
+ - executing==1.2.0
50
+ - filelock==3.12.2
51
+ - fonttools==4.45.1
52
+ - frozenlist==1.4.1
53
+ - fsspec==2023.10.0
54
+ - funcsigs==1.0.2
55
+ - future==0.18.3
56
+ - gmpy2==2.1.0
57
+ - gpustat==1.1.1
58
+ - huggingface-hub==0.25.0
59
+ - idna==3.6
60
+ - importlib-metadata==6.0.0
61
+ - importlib-resources==6.4.5
62
+ - iniconfig==2.0.0
63
+ - ipykernel==6.23.1
64
+ - ipython==8.0.0
65
+ - jedi==0.18.2
66
+ - jinja2==3.1.2
67
+ - joblib==1.3.2
68
+ - jupyter-client==8.1.0
69
+ - jupyter-core==5.1.0
70
+ - kiwisolver==1.4.5
71
+ - latex==0.7.0
72
+ - latexcodec==1.0.0
73
+ - lit==18.1.8
74
+ - markupsafe==2.1.2
75
+ - matplotlib==3.8.2
76
+ - matplotlib-inline==0.1.6
77
+ - mizani==0.9.3
78
+ - mpmath==1.2.1
79
+ - multidict==6.1.0
80
+ - multiprocess==0.70.16
81
+ - mypy-extensions==1.0.0
82
+ - nest-asyncio==1.5.6
83
+ - networkx==2.8.6
84
+ - nltk==3.8.1
85
+ - numpy==1.26.2
86
+ - nvidia-cublas-cu11==11.10.3.66
87
+ - nvidia-cuda-cupti-cu11==11.7.101
88
+ - nvidia-cuda-nvrtc-cu11==11.7.99
89
+ - nvidia-cuda-runtime-cu11==11.7.99
90
+ - nvidia-cudnn-cu11==8.5.0.96
91
+ - nvidia-cufft-cu11==10.9.0.58
92
+ - nvidia-curand-cu11==10.2.10.91
93
+ - nvidia-cusolver-cu11==11.4.0.1
94
+ - nvidia-cusparse-cu11==11.7.4.91
95
+ - nvidia-ml-py==12.560.30
96
+ - nvidia-nccl-cu11==2.14.3
97
+ - nvidia-nvtx-cu11==11.7.91
98
+ - packaging==23.0
99
+ - pandas==2.1.3
100
+ - parso==0.8.3
101
+ - pathspec==0.12.1
102
+ - patsy==0.5.3
103
+ - peft==0.13.0
104
+ - pexpect==4.8.0
105
+ - pickleshare==0.7.5
106
+ - pillow==10.1.0
107
+ - platformdirs==2.5.2
108
+ - plotnine==0.12.4
109
+ - pluggy==1.3.0
110
+ - pluralizer==1.2.0
111
+ - prompt-toolkit==3.0.30
112
+ - protobuf==4.25.1
113
+ - psutil==5.9.1
114
+ - ptyprocess==0.7.0
115
+ - pure-eval==0.2.2
116
+ - pyarrow==17.0.0
117
+ - pygments==2.15.0
118
+ - pyparsing==3.1.1
119
+ - pytest==7.4.3
120
+ - python-dateutil==2.8.2
121
+ - pytz==2023.3.post1
122
+ - pyyaml==6.0.1
123
+ - pyzmq==23.0.0
124
+ - regex==2023.10.3
125
+ - requests==2.32.3
126
+ - safetensors==0.4.5
127
+ - scikit-learn==1.3.2
128
+ - scipy==1.11.4
129
+ - seaborn==0.13.0
130
+ - sentencepiece==0.2.0
131
+ - shutilwhich==1.1.0
132
+ - six==1.16.0
133
+ - stack-data==0.6.0
134
+ - stanza==1.9.2
135
+ - statsmodels==0.14.0
136
+ - sympy==1.11.1
137
+ - tempdir==0.7.1
138
+ - threadpoolctl==3.2.0
139
+ - tokenizers==0.20.0
140
+ - tomli==2.0.1
141
+ - torch==2.0.0
142
+ - tornado==6.2
143
+ - tqdm==4.66.5
144
+ - traitlets==5.7.1
145
+ - transformers==4.45.1
146
+ - triton==2.0.0
147
+ - typing-extensions==4.6.0
148
+ - tzdata==2023.3
149
+ - urllib3==2.1.0
150
+ - wcwidth==0.2.5
151
+ - xxhash==3.5.0
152
+ - yarl==1.13.0
153
+ - zipp==3.12.0
154
+ prefix: /home/yiren/new_ssd2/chunhui/miniconda/envs/impossible_llm
impossible_llm_update.yaml ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: impossible_llm
2
+ channels:
3
+ - defaults
4
+ dependencies:
5
+ - _libgcc_mutex=0.1=main
6
+ - _openmp_mutex=5.1=1_gnu
7
+ - ca-certificates=2024.7.2=h06a4308_0
8
+ - ld_impl_linux-64=2.38=h1181459_1
9
+ - libffi=3.4.4=h6a678d5_1
10
+ - libgcc-ng=11.2.0=h1234567_1
11
+ - libgomp=11.2.0=h1234567_1
12
+ - libstdcxx-ng=11.2.0=h1234567_1
13
+ - ncurses=6.4=h6a678d5_0
14
+ - openssl=3.0.15=h5eee18b_0
15
+ - pip=24.2=py39h06a4308_0
16
+ - python=3.9.19=h955ad1f_1
17
+ - readline=8.2=h5eee18b_0
18
+ - setuptools=72.1.0=py39h06a4308_0
19
+ - sqlite=3.45.3=h5eee18b_0
20
+ - tk=8.6.14=h39e8969_0
21
+ - wheel=0.44.0=py39h06a4308_0
22
+ - xz=5.4.6=h5eee18b_1
23
+ - zlib=1.2.13=h5eee18b_1
24
+ - pip:
25
+ - accelerate==1.0.0
26
+ - aiohappyeyeballs==2.4.2
27
+ - aiohttp==3.10.6
28
+ - aiosignal==1.3.1
29
+ - annotated-types==0.7.0
30
+ - asttokens==2.2.1
31
+ - async-timeout==4.0.3
32
+ - attrs==24.2.0
33
+ - backcall==0.2.0
34
+ - black==24.8.0
35
+ - blessed==1.20.0
36
+ - certifi==2023.11.17
37
+ - charset-normalizer==3.3.2
38
+ - click==8.1.7
39
+ - cmake==3.30.3
40
+ - comm==0.1.2
41
+ - contourpy==1.2.0
42
+ - cycler==0.12.1
43
+ - data==0.4
44
+ - datasets==3.0.1
45
+ - debugpy==1.6.7
46
+ - decorator==5.1.1
47
+ - deepspeed==0.15.2
48
+ - dill==0.3.8
49
+ - emoji==2.8.0
50
+ - exceptiongroup==1.1.0
51
+ - executing==1.2.0
52
+ - filelock==3.12.2
53
+ - fonttools==4.45.1
54
+ - frozenlist==1.4.1
55
+ - fsspec==2023.10.0
56
+ - funcsigs==1.0.2
57
+ - future==0.18.3
58
+ - gmpy2==2.1.0
59
+ - gpustat==1.1.1
60
+ - hjson==3.1.0
61
+ - huggingface-hub==0.25.0
62
+ - idna==3.6
63
+ - importlib-metadata==6.0.0
64
+ - importlib-resources==6.4.5
65
+ - iniconfig==2.0.0
66
+ - ipykernel==6.23.1
67
+ - ipython==8.0.0
68
+ - jedi==0.18.2
69
+ - jinja2==3.1.2
70
+ - joblib==1.3.2
71
+ - jupyter-client==8.1.0
72
+ - jupyter-core==5.1.0
73
+ - kiwisolver==1.4.5
74
+ - latex==0.7.0
75
+ - latexcodec==1.0.0
76
+ - lit==18.1.8
77
+ - markupsafe==2.1.2
78
+ - matplotlib==3.8.2
79
+ - matplotlib-inline==0.1.6
80
+ - mizani==0.9.3
81
+ - mpmath==1.2.1
82
+ - msgpack==1.1.0
83
+ - multidict==6.1.0
84
+ - multiprocess==0.70.16
85
+ - mypy-extensions==1.0.0
86
+ - nest-asyncio==1.5.6
87
+ - networkx==2.8.6
88
+ - ninja==1.11.1.1
89
+ - nltk==3.8.1
90
+ - numpy==1.26.2
91
+ - nvidia-cublas-cu11==11.10.3.66
92
+ - nvidia-cuda-cupti-cu11==11.7.101
93
+ - nvidia-cuda-nvrtc-cu11==11.7.99
94
+ - nvidia-cuda-runtime-cu11==11.7.99
95
+ - nvidia-cudnn-cu11==8.5.0.96
96
+ - nvidia-cufft-cu11==10.9.0.58
97
+ - nvidia-curand-cu11==10.2.10.91
98
+ - nvidia-cusolver-cu11==11.4.0.1
99
+ - nvidia-cusparse-cu11==11.7.4.91
100
+ - nvidia-ml-py==12.560.30
101
+ - nvidia-nccl-cu11==2.14.3
102
+ - nvidia-nvtx-cu11==11.7.91
103
+ - packaging==23.0
104
+ - pandas==2.1.3
105
+ - parso==0.8.3
106
+ - pathspec==0.12.1
107
+ - patsy==0.5.3
108
+ - peft==0.13.0
109
+ - pexpect==4.8.0
110
+ - pickleshare==0.7.5
111
+ - pillow==10.1.0
112
+ - platformdirs==2.5.2
113
+ - plotnine==0.12.4
114
+ - pluggy==1.3.0
115
+ - pluralizer==1.2.0
116
+ - prompt-toolkit==3.0.30
117
+ - protobuf==4.25.1
118
+ - psutil==5.9.1
119
+ - ptyprocess==0.7.0
120
+ - pure-eval==0.2.2
121
+ - py-cpuinfo==9.0.0
122
+ - pyarrow==17.0.0
123
+ - pydantic==2.9.2
124
+ - pydantic-core==2.23.4
125
+ - pygments==2.15.0
126
+ - pyparsing==3.1.1
127
+ - pytest==7.4.3
128
+ - python-dateutil==2.8.2
129
+ - pytz==2023.3.post1
130
+ - pyyaml==6.0.1
131
+ - pyzmq==23.0.0
132
+ - regex==2023.10.3
133
+ - requests==2.32.3
134
+ - safetensors==0.4.5
135
+ - scikit-learn==1.3.2
136
+ - scipy==1.11.4
137
+ - seaborn==0.13.0
138
+ - sentencepiece==0.2.0
139
+ - shutilwhich==1.1.0
140
+ - six==1.16.0
141
+ - stack-data==0.6.0
142
+ - stanza==1.9.2
143
+ - statsmodels==0.14.0
144
+ - sympy==1.11.1
145
+ - tempdir==0.7.1
146
+ - threadpoolctl==3.2.0
147
+ - tokenizers==0.20.0
148
+ - tomli==2.0.1
149
+ - torch==2.0.0
150
+ - tornado==6.2
151
+ - tqdm==4.66.5
152
+ - traitlets==5.7.1
153
+ - transformers==4.45.1
154
+ - triton==2.0.0
155
+ - typing-extensions==4.12.2
156
+ - tzdata==2023.3
157
+ - urllib3==2.1.0
158
+ - wcwidth==0.2.5
159
+ - xxhash==3.5.0
160
+ - yarl==1.13.0
161
+ - zipp==3.12.0
162
+ prefix: /home/yiren/new_ssd2/chunhui/miniconda/envs/impossible_llm
perplexities/demo.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import sys
3
+ import argparse
4
+ import os
5
+ sys.path.append("..")
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling
7
+ from datasets import load_dataset
8
+ from numpy.random import default_rng
9
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
10
+
11
+ MODEL_NAME_SAVE = "Llama-3.2-3B"
12
+ FILE_SAMPLE_SIZE = 5
13
+
14
+ def get_perplexities(model, eval_dataset, batch_size):
15
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
16
+
17
+ training_args = TrainingArguments(
18
+ output_dir="./tmp_trainer",
19
+ per_device_eval_batch_size=batch_size,
20
+ fp16=True,
21
+ report_to="none"
22
+ )
23
+
24
+ trainer = Trainer(model=model, args=training_args, eval_dataset=eval_dataset, data_collator=data_collator)
25
+ eval_results = trainer.evaluate()
26
+ print("eval_results:", eval_results)
27
+ loss = eval_results['eval_loss']
28
+ perplexity = torch.exp(torch.tensor(loss)).item()
29
+ return perplexity
30
+
31
+ if __name__ == "__main__":
32
+ parser = argparse.ArgumentParser(description="Calculate perplexity on test dataset.")
33
+
34
+ parser.add_argument('perturbation',
35
+ type=str,
36
+ default='reverse_full',
37
+ nargs='?',
38
+ help='Type of perturbation to use.')
39
+ parser.add_argument('train_set',
40
+ type=str,
41
+ default='test',
42
+ nargs='?',
43
+ help='Dataset size for training.')
44
+ parser.add_argument('batch_size',
45
+ type=int,
46
+ default=4,
47
+ nargs='?',
48
+ help='Batch size for evaluation.')
49
+ parser.add_argument('seed',
50
+ type=int,
51
+ default=0,
52
+ nargs='?',
53
+ help='Random seed.')
54
+
55
+ args = parser.parse_args()
56
+
57
+ dataset_name = f"babylm_{args.perturbation}_{args.train_set}_seed{args.seed}"
58
+ dataset = load_dataset('../train/babylm_dataset_test.py', name=dataset_name, trust_remote_code=True)
59
+ test_dataset = dataset['test'] # Load test dataset
60
+ print(test_dataset)
61
+
62
+ checkpoint_path = f'../train/checkpoints/{MODEL_NAME_SAVE}/babylm_{args.perturbation}_10M_seed0/runs/checkpoint-450'
63
+
64
+ rng = default_rng(args.seed)
65
+ indices = rng.choice(len(test_dataset), FILE_SAMPLE_SIZE, replace=False)
66
+ sampled_test_dataset = test_dataset.select(indices)
67
+
68
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
69
+ model = AutoModelForCausalLM.from_pretrained(checkpoint_path)
70
+
71
+ model.eval()
72
+ if torch.cuda.is_available():
73
+ model.to('cuda')
74
+
75
+ def tokenize_function(examples):
76
+ return tokenizer(examples['text'], padding="max_length", truncation=True, max_length=1024)
77
+
78
+ tokenized_test = sampled_test_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
79
+
80
+
81
+ perplexity = get_perplexities(model, tokenized_test, 1)
82
+ print(f"Perplexity on test set: {perplexity}")
perplexities/gpt2_mask.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import sys
3
+ import argparse
4
+ import os
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ from tqdm import tqdm
8
+ import gc
9
+ from numpy.random import default_rng
10
+ sys.path.append("..")
11
+ from transformers import (
12
+ AutoTokenizer,
13
+ AutoModelForCausalLM,
14
+ Trainer,
15
+ TrainingArguments,
16
+ DataCollatorForLanguageModeling
17
+ )
18
+ from datasets import load_dataset
19
+
20
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
21
+
22
+ class LayerMasker:
23
+ def __init__(self, model):
24
+ self.model = model
25
+ self.original_forwards = {} # Store original forward methods
26
+
27
+ def _mask_ffn(self, layer, mask_strength=1.0):
28
+ """Correctly replace FFN forward propagation"""
29
+ layer_id = id(layer.mlp)
30
+ if layer_id not in self.original_forwards:
31
+ self.original_forwards[layer_id] = layer.mlp.forward # Store original function
32
+
33
+ def masked_forward(x):
34
+ return self.original_forwards[layer_id](x) * (1 - mask_strength)
35
+
36
+ layer.mlp.forward = masked_forward # Override with masked function
37
+
38
+ def _mask_attn(self, layer, mask_strength=1.0):
39
+ """Correctly replace Attention forward propagation"""
40
+ layer_id = id(layer.attn)
41
+ if layer_id not in self.original_forwards:
42
+ self.original_forwards[layer_id] = layer.attn.forward # Store original function
43
+
44
+ def masked_forward(hidden_states, **kwargs):
45
+ output = self.original_forwards[layer_id](hidden_states, **kwargs)
46
+ masked_output = (output[0] * (1 - mask_strength),) + output[1:]
47
+ return masked_output
48
+
49
+ layer.attn.forward = masked_forward # Override with masked function
50
+
51
+ def mask_layer(self, layer_idx, module_type="ffn", mask_strength=1.0):
52
+ layer = self.model.transformer.h[layer_idx]
53
+ if module_type == "ffn":
54
+ self._mask_ffn(layer, mask_strength)
55
+ elif module_type == "attn":
56
+ self._mask_attn(layer, mask_strength)
57
+ else:
58
+ raise ValueError(f"Invalid module type: {module_type}")
59
+
60
+ def reset(self):
61
+ """Restore all modified forward methods"""
62
+ for layer in self.model.transformer.h:
63
+ layer_id = id(layer.mlp)
64
+ if layer_id in self.original_forwards:
65
+ layer.mlp.forward = self.original_forwards[layer_id]
66
+
67
+ layer_id = id(layer.attn)
68
+ if layer_id in self.original_forwards:
69
+ layer.attn.forward = self.original_forwards[layer_id]
70
+
71
+ self.original_forwards.clear()
72
+
73
+ @torch.no_grad() # Disable gradient computation
74
+ def get_perplexities(model, eval_dataset, batch_size):
75
+ """Compute perplexity for the dataset"""
76
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
77
+
78
+ training_args = TrainingArguments(
79
+ output_dir="./tmp_trainer",
80
+ per_device_eval_batch_size=batch_size,
81
+ fp16=torch.cuda.is_available(),
82
+ report_to="none"
83
+ )
84
+
85
+ trainer = Trainer(
86
+ model=model,
87
+ args=training_args,
88
+ eval_dataset=eval_dataset,
89
+ data_collator=data_collator
90
+ )
91
+
92
+ eval_results = trainer.evaluate()
93
+ loss = eval_results['eval_loss']
94
+ return torch.exp(torch.tensor(loss)).item()
95
+
96
+ def run_mask_experiment(model, tokenized_data, args):
97
+ """Run layer masking experiment"""
98
+ results = []
99
+ masker = LayerMasker(model)
100
+
101
+ # Compute baseline perplexity
102
+ baseline_ppl = get_perplexities(model, tokenized_data, args.batch_size)
103
+ print(f"Baseline Perplexity: {baseline_ppl:.2f}")
104
+
105
+ # Layer masking experiments
106
+ print("\nRunning layer masking experiments:")
107
+ for layer_idx in tqdm(range(model.config.n_layer), desc="Layers"):
108
+ for module_type in ["ffn", "attn"]:
109
+ # Apply masking
110
+ masker.mask_layer(layer_idx, module_type)
111
+
112
+ # Compute perplexity
113
+ ppl = get_perplexities(model, tokenized_data, args.batch_size)
114
+ delta = ppl - baseline_ppl
115
+
116
+ gc.collect()
117
+ torch.cuda.empty_cache()
118
+
119
+ results.append({
120
+ "layer": layer_idx,
121
+ "module": module_type,
122
+ "perplexity": ppl,
123
+ "delta": delta
124
+ })
125
+
126
+ # Reset the model
127
+ masker.reset()
128
+
129
+ return results, baseline_ppl
130
+
131
+ def visualize_results(results_df, output_dir):
132
+ """Visualize layer impact results"""
133
+ plt.figure(figsize=(12, 6))
134
+
135
+ # Plot FFN curve
136
+ ffn_data = results_df[results_df["module"] == "ffn"]
137
+ plt.plot(ffn_data["layer"], ffn_data["delta"],
138
+ marker='o', linestyle='-', linewidth=2, markersize=8,
139
+ color='#1f77b4', label='FFN')
140
+
141
+ # Plot Attention curve
142
+ attn_data = results_df[results_df["module"] == "attn"]
143
+ plt.plot(attn_data["layer"], attn_data["delta"],
144
+ marker='s', linestyle='--', linewidth=2, markersize=8,
145
+ color='#ff7f0e', label='Attention')
146
+
147
+ plt.xlabel("Layer Index", fontsize=12)
148
+ plt.ylabel("Δ Perplexity (log scale)", fontsize=12)
149
+ plt.title("GPT-2 Layer Masking Impact Analysis", fontsize=14)
150
+ plt.legend(fontsize=10)
151
+ plt.grid(True, alpha=0.3)
152
+ plt.xticks(range(0, results_df["layer"].max()+1))
153
+
154
+ # Set y-axis to log scale
155
+ plt.yscale('log')
156
+
157
+ plot_path = os.path.join(output_dir, f"{args.perturbation}_layer_impact.png")
158
+ plt.savefig(plot_path, bbox_inches='tight', dpi=300)
159
+ plt.close()
160
+
161
+
162
+ if __name__ == "__main__":
163
+ # Argument configuration
164
+ parser = argparse.ArgumentParser(description="GPT-2 Layer Masking Experiment")
165
+ parser.add_argument('--perturbation', type=str, default="hop_sg2pl",
166
+ help="Perturbation type (default: hop_sg2pl)")
167
+ parser.add_argument('--train_set', type=str, default="10M",
168
+ help="Training set size (default: 10M)")
169
+ parser.add_argument('--checkpoint_path', type=str, default="checkpoint-2736",
170
+ help="Model checkpoint path (default: checkpoint-2736)")
171
+ parser.add_argument('--batch_size', type=int, default=3,
172
+ help="Evaluation batch size (default: 3)")
173
+ parser.add_argument('--seed', type=int, default=0,
174
+ help="Random seed (default: 0)")
175
+ args = parser.parse_args()
176
+
177
+ # Initialize output directory
178
+ output_dir = f"mask_results/GPT2-MaskResults"
179
+ os.makedirs(output_dir, exist_ok=True)
180
+
181
+ # Load dataset
182
+ dataset_name = f"babylm_{args.perturbation}_{args.train_set}_seed{args.seed}"
183
+ dataset = load_dataset('../train/babylm_dataset_test.py', name=dataset_name, trust_remote_code=True)
184
+ test_dataset = dataset['test']
185
+
186
+ # Data sampling
187
+ rng = default_rng(args.seed)
188
+ indices = rng.choice(len(test_dataset), size=500, replace=False)
189
+ sampled_test = test_dataset.select(indices)
190
+
191
+ # Load model and tokenizer
192
+ checkpoint_path = f"../train/checkpoints/GPT-2/babylm_{args.perturbation}_10M_seed0/runs/{args.checkpoint_path}"
193
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
194
+ model = AutoModelForCausalLM.from_pretrained(checkpoint_path, torch_dtype=torch.float16).eval()
195
+ if torch.cuda.is_available():
196
+ model = model.cuda()
197
+
198
+ # Data preprocessing
199
+ def tokenize_fn(examples):
200
+ return tokenizer(
201
+ examples["text"],
202
+ padding="max_length",
203
+ truncation=True,
204
+ max_length=512,
205
+ return_tensors="pt"
206
+ )
207
+
208
+ tokenized_test = sampled_test.map(
209
+ tokenize_fn,
210
+ batched=True,
211
+ remove_columns=["text"],
212
+ desc="Tokenizing"
213
+ )
214
+
215
+ # Run experiment
216
+ results, baseline_ppl = run_mask_experiment(model, tokenized_test, args)
217
+ results_df = pd.DataFrame(results)
218
+
219
+ # Save results
220
+ output_file = os.path.join(output_dir, f"mask_results_{args.perturbation}.csv")
221
+ results_df.to_csv(output_file, index=False)
222
+
223
+ # Generate visualization
224
+ visualize_results(results_df, output_dir)
225
+
226
+ # Print key results
227
+ print(f"\nExperiment completed. Results saved to {output_dir}")
228
+ print(f"Baseline Perplexity: {baseline_ppl:.2f}")
229
+ print("Layer impact results:")
230
+ print(results_df[["layer", "module", "delta"]].to_string(index=False))
231
+
perplexities/gpt2_mask_test.py ADDED
File without changes
perplexities/gpt2_noise.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Activation Steering
2
+
3
+ import torch
4
+ import sys
5
+ import argparse
6
+ import os
7
+ import pandas as pd
8
+ import matplotlib.pyplot as plt
9
+ from tqdm import tqdm
10
+ from numpy.random import default_rng
11
+ sys.path.append("..")
12
+ from transformers import (
13
+ AutoTokenizer,
14
+ AutoModelForCausalLM,
15
+ Trainer,
16
+ TrainingArguments,
17
+ DataCollatorForLanguageModeling
18
+ )
19
+ from datasets import load_dataset
20
+
21
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
22
+
23
+ class LayerNoiseInjector:
24
+
25
+ def __init__(self, model):
26
+ self.model = model
27
+ self.hooks = [] # Store registered hooks
28
+ self.noise_std = 0.1 # Noise standard deviation (adjustable)
29
+
30
+
31
+ def _add_noise_hook(self, layer_idx):
32
+ def hook(module, input, output):
33
+ if isinstance(output, tuple):
34
+ modified_output = list(output)
35
+ noise = torch.randn_like(modified_output[0]) * self.noise_std
36
+ modified_output[0] = modified_output[0] + noise
37
+ return tuple(modified_output)
38
+ else:
39
+ noise = torch.randn_like(output) * self.noise_std
40
+ return output + noise
41
+
42
+ handle = self.model.transformer.h[layer_idx].register_forward_hook(hook)
43
+ self.hooks.append(handle)
44
+
45
+
46
+ def inject_noise(self, layer_idx):
47
+ """Inject noise to specified layer"""
48
+ self._add_noise_hook(layer_idx)
49
+
50
+ def reset(self):
51
+ """Remove all noise hooks"""
52
+ for handle in self.hooks:
53
+ handle.remove()
54
+ self.hooks.clear()
55
+
56
+ @torch.no_grad() # Disable gradient calculation
57
+ def get_perplexities(model, eval_dataset, batch_size):
58
+ """Calculate dataset perplexity"""
59
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
60
+
61
+ training_args = TrainingArguments(
62
+ output_dir="./tmp_trainer",
63
+ per_device_eval_batch_size=batch_size,
64
+ fp16=torch.cuda.is_available(),
65
+ report_to="none"
66
+ )
67
+
68
+ trainer = Trainer(
69
+ model=model,
70
+ args=training_args,
71
+ eval_dataset=eval_dataset,
72
+ data_collator=data_collator
73
+ )
74
+
75
+ eval_results = trainer.evaluate()
76
+ loss = eval_results['eval_loss']
77
+ return torch.exp(torch.tensor(loss)).item()
78
+
79
+ # Data preprocessing
80
+ def tokenize_fn(examples):
81
+ return tokenizer(
82
+ examples["text"],
83
+ padding="max_length",
84
+ truncation=True,
85
+ max_length=512,
86
+ return_tensors="pt"
87
+ )
88
+
89
+
90
+ def run_noise_experiment(model, tokenized_data, args):
91
+ """Execute layer-wise noise injection experiment"""
92
+ results = []
93
+ noise_injector = LayerNoiseInjector(model)
94
+
95
+ # Calculate baseline perplexity
96
+ baseline_ppl = get_perplexities(model, tokenized_data, args.batch_size)
97
+ print(f"Baseline Perplexity: {baseline_ppl:.2f}")
98
+
99
+ # Layer-wise noise injection experiment
100
+ print("\nRunning layer noise injection experiments:")
101
+ for layer_idx in tqdm(range(model.config.n_layer), desc="Layers"):
102
+ # Inject noise
103
+ noise_injector.inject_noise(layer_idx)
104
+
105
+ # Calculate perplexity
106
+ ppl = get_perplexities(model, tokenized_data, args.batch_size)
107
+ delta = ppl - baseline_ppl
108
+
109
+ results.append({
110
+ "layer": layer_idx,
111
+ "perplexity": ppl,
112
+ "delta": delta
113
+ })
114
+
115
+ # Reset model
116
+ noise_injector.reset()
117
+
118
+ return results, baseline_ppl
119
+
120
+ def visualize_noise_results(results_df, output_dir):
121
+ """Visualize noise injection impact results"""
122
+ plt.figure(figsize=(12, 6))
123
+
124
+ # Plot delta perplexity curve
125
+ plt.plot(results_df["layer"], results_df["delta"],
126
+ marker='o', linestyle='-', linewidth=2, markersize=8,
127
+ color='#2ca02c', label='Activation Noise')
128
+
129
+ plt.xlabel("Layer Index", fontsize=12)
130
+ plt.ylabel("Δ Perplexity (log scale)", fontsize=12)
131
+ plt.title("GPT-2 Layer Noise Injection Impact Analysis", fontsize=14)
132
+ plt.legend(fontsize=10)
133
+ plt.grid(True, alpha=0.3)
134
+ plt.xticks(range(0, results_df["layer"].max()+1))
135
+
136
+ # Set y-axis to log scale
137
+ plt.yscale('log')
138
+
139
+ plot_path = os.path.join(output_dir, f"{args.perturbation}_noise_impact.png")
140
+ plt.savefig(plot_path, bbox_inches='tight', dpi=300)
141
+ plt.close()
142
+
143
+ if __name__ == "__main__":
144
+ # Parameter configuration
145
+ parser = argparse.ArgumentParser(description="GPT-2 Layer Noise Injection Experiment")
146
+ parser.add_argument('--perturbation', type=str, default="hop_sg2pl",
147
+ help="Perturbation type (default: hop_sg2pl)")
148
+ parser.add_argument('--train_set', type=str, default="10M",
149
+ help="Training set size (default: 10M)")
150
+ parser.add_argument('--checkpoint_path', type=str, default="checkpoint-2736",
151
+ help="Model checkpoint path (default: checkpoint-2736)")
152
+ parser.add_argument('--batch_size', type=int, default=3,
153
+ help="Evaluation batch size (default: 3)")
154
+ parser.add_argument('--seed', type=int, default=0,
155
+ help="Random seed (default: 0)")
156
+ parser.add_argument('--noise_std', type=float, default=0.1,
157
+ help="Noise standard deviation (default: 0.1)")
158
+ args = parser.parse_args()
159
+
160
+ # Initialize output directory
161
+ output_dir = f"noise_results/GPT2-NoiseResults"
162
+ os.makedirs(output_dir, exist_ok=True)
163
+
164
+ # Load dataset
165
+ dataset_name = f"babylm_{args.perturbation}_{args.train_set}_seed{args.seed}"
166
+ dataset = load_dataset('../train/babylm_dataset_test.py', name=dataset_name, trust_remote_code=True)
167
+ test_dataset = dataset['test']
168
+
169
+ # Data sampling
170
+ rng = default_rng(args.seed)
171
+ indices = rng.choice(len(test_dataset), size=500, replace=False)
172
+ sampled_test = test_dataset.select(indices)
173
+
174
+ # Load model and tokenizer
175
+ checkpoint_path = f"../train/checkpoints/GPT-2/babylm_{args.perturbation}_10M_seed0/runs/{args.checkpoint_path}"
176
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
177
+ model = AutoModelForCausalLM.from_pretrained(checkpoint_path, torch_dtype=torch.float16).eval()
178
+ if torch.cuda.is_available():
179
+ model = model.cuda()
180
+
181
+ tokenized_test = sampled_test.map(
182
+ tokenize_fn,
183
+ batched=True,
184
+ remove_columns=["text"],
185
+ desc="Tokenizing"
186
+ )
187
+
188
+ # Run noise injection experiment
189
+ results, baseline_ppl = run_noise_experiment(model, tokenized_test, args)
190
+ results_df = pd.DataFrame(results)
191
+
192
+ # Save results
193
+ output_file = os.path.join(output_dir, f"noise_results_{args.perturbation}.csv")
194
+ results_df.to_csv(output_file, index=False)
195
+
196
+ # Generate visualization
197
+ visualize_noise_results(results_df, output_dir)
198
+
199
+ # Print key results
200
+ print(f"\nExperiment completed. Results saved to {output_dir}")
201
+ print(f"Baseline Perplexity: {baseline_ppl:.2f}")
202
+ print("Layer impact results:")
203
+ print(results_df[["layer", "delta"]].to_string(index=False))
perplexities/gpt2_remove_layers.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import sys
3
+ import argparse
4
+ import os
5
+ sys.path.append("..")
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling
7
+ from datasets import load_dataset
8
+ from numpy.random import default_rng
9
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
10
+
11
+ # MODEL_NAME = "Llama-3.2-3B"
12
+ # ori_model_name = "meta-llama/Llama-3.2-3B"
13
+ # MODEL_NAME_SAVE = "Llama-3.2-3B-500-Remove"
14
+
15
+ MODEL_NAME = "GPT-2"
16
+ ori_model_name = "gpt2"
17
+ MODEL_NAME_SAVE = "GPT2-500-Remove-layers"
18
+
19
+ FILE_SAMPLE_SIZE = 500
20
+
21
+
22
+ def remove_layers(original_model, exp_model, i, j):
23
+
24
+ original_blocks = original_model.transformer.h
25
+ exp_blocks = exp_model.transformer.h
26
+
27
+ print("len(original_blocks):", len(original_blocks))
28
+ print("len(exp_blocks):", len(exp_blocks))
29
+
30
+ # Ensure the indices are valid
31
+ if i < 0 or (i + j) > len(exp_blocks) or j < 0:
32
+ raise ValueError(f"Invalid block indices: i={i}, i+j={i+j}. Must satisfy 0 <= i and i+j <= {len(exp_blocks)}.")
33
+
34
+ # Replace the parameters of the blocks from i to i+j
35
+ for idx in range(i, i + j):
36
+ print(f"Replacing parameters of Transformer block {idx}...")
37
+ original_block = original_blocks[idx]
38
+ exp_blocks[idx].load_state_dict(original_block.state_dict())
39
+
40
+ return exp_model
41
+
42
+
43
+
44
+ def get_perplexities(model, eval_dataset, batch_size):
45
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
46
+
47
+ training_args = TrainingArguments(
48
+ output_dir="./tmp_trainer",
49
+ per_device_eval_batch_size=batch_size,
50
+ fp16=True,
51
+ report_to="none"
52
+ )
53
+
54
+ trainer = Trainer(model=model, args=training_args, eval_dataset=eval_dataset, data_collator=data_collator)
55
+ eval_results = trainer.evaluate()
56
+ print("eval_results:", eval_results)
57
+ loss = eval_results['eval_loss']
58
+ perplexity = torch.exp(torch.tensor(loss)).item()
59
+ return perplexity
60
+
61
+ if __name__ == "__main__":
62
+ parser = argparse.ArgumentParser(description="Calculate perplexity on test dataset.")
63
+
64
+ parser.add_argument('perturbation',
65
+ type=str,
66
+ default='reverse_full',
67
+ nargs='?',
68
+ help='Type of perturbation to use.')
69
+ parser.add_argument('train_set',
70
+ type=str,
71
+ default='test',
72
+ nargs='?',
73
+ help='Dataset size for training.')
74
+ parser.add_argument('checkpoint_path',
75
+ type=str,
76
+ default='checkpoint-100',
77
+ nargs='?',
78
+ help='Dataset size for training.')
79
+ parser.add_argument('batch_size',
80
+ type=int,
81
+ default=4,
82
+ nargs='?',
83
+ help='Batch size for evaluation.')
84
+ parser.add_argument('seed',
85
+ type=int,
86
+ default=0,
87
+ nargs='?',
88
+ help='Random seed.')
89
+ parser.add_argument('remove_layer',
90
+ type=int,
91
+ default=1,
92
+ nargs='?',
93
+ help='Layer index to remove')
94
+ args = parser.parse_args()
95
+
96
+ dataset_name = f"babylm_{args.perturbation}_{args.train_set}_seed{args.seed}"
97
+ dataset = load_dataset('../train/babylm_dataset_test.py', name=dataset_name, trust_remote_code=True)
98
+ test_dataset = dataset['test'] # Load test dataset
99
+ print(test_dataset)
100
+
101
+
102
+ checkpoint_path = f'../train/checkpoints/{MODEL_NAME}/babylm_{args.perturbation}_10M_seed0/runs/{args.checkpoint_path}'
103
+
104
+ rng = default_rng(args.seed)
105
+ indices = rng.choice(len(test_dataset), FILE_SAMPLE_SIZE, replace=False)
106
+ sampled_test_dataset = test_dataset.select(indices)
107
+
108
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
109
+ model = AutoModelForCausalLM.from_pretrained(checkpoint_path)
110
+ model_ori = AutoModelForCausalLM.from_pretrained(ori_model_name)
111
+ model = remove_layers(model_ori, model, args.remove_layer, 4)
112
+ print(model)
113
+ model.eval()
114
+ # if torch.cuda.is_available():
115
+ # model.to('cuda')
116
+
117
+ def tokenize_function(examples):
118
+ return tokenizer(examples['text'], padding="max_length", truncation=True, max_length=1024)
119
+
120
+ tokenized_test = sampled_test_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
121
+
122
+
123
+ perplexity = get_perplexities(model, tokenized_test, args.batch_size)
124
+
125
+ # Save the result to the specified directory
126
+ output_directory = f"perplexities_results/{MODEL_NAME_SAVE}"
127
+ os.makedirs(output_directory, exist_ok=True)
128
+
129
+ # Construct the output file path
130
+ output_file = os.path.join(output_directory, f"{args.perturbation}_{args.batch_size}_{args.seed}.csv")
131
+
132
+ # Write the header to the CSV file if it doesn't exist
133
+ if not os.path.exists(output_file):
134
+ with open(output_file, 'w') as f:
135
+ print("Writing header to CSV...")
136
+ f.write(f"checkpoint_path, perplexity\n")
137
+
138
+ # Append the perplexity result to the CSV file
139
+ with open(output_file, 'a') as f: # Open in append mode
140
+ print("Appending result to CSV...")
141
+ f.write(f"{args.checkpoint_path}-{args.remove_layer+1}, {perplexity}\n")
perplexities/gpt_mask.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ CUDA_VISIBLE_DEVICES=1,2 torchrun --nproc_per_node=2 --master_port=22235 gpt2_mask.py --perturbation shuffle_nondeterministic --train_set 10M --checkpoint_path checkpoint-2736 --batch_size 3 --seed 0
4
+
5
+ CUDA_VISIBLE_DEVICES=1,2 torchrun --nproc_per_node=2 --master_port=22235 gpt2_mask.py --perturbation shuffle_nondeterministic --train_set 10M --checkpoint_path checkpoint-2736 --batch_size 3 --seed 0
6
+
7
+ CUDA_VISIBLE_DEVICES=1,2 torchrun --nproc_per_node=2 --master_port=22235 gpt2_mask.py --perturbation hop_words4 --train_set 10M --checkpoint_path checkpoint-2376 --batch_size 3 --seed 0
8
+
9
+ CUDA_VISIBLE_DEVICES=1,2 torchrun --nproc_per_node=2 --master_port=22235 gpt2_mask.py --perturbation hop_control --train_set 10M --checkpoint_path checkpoint-2409 --batch_size 3 --seed 0
perplexities/gpt_noise.sh ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ CUDA_VISIBLE_DEVICES=2,3 torchrun --nproc_per_node=2 --master_port=25235 gpt2_noise.py --perturbation shuffle_nondeterministic --train_set 10M --checkpoint_path checkpoint-2736 --batch_size 3 --seed 0 --noise_std 0.1
2
+
3
+ CUDA_VISIBLE_DEVICES=2,3 torchrun --nproc_per_node=2 --master_port=25236 gpt2_noise.py --perturbation shuffle_control --train_set 10M --checkpoint_path checkpoint-2736 --batch_size 3 --seed 0 --noise_std 0.1
4
+
5
+ CUDA_VISIBLE_DEVICES=2,3 torchrun --nproc_per_node=2 --master_port=25237 gpt2_noise.py --perturbation hop_words4 --train_set 10M --checkpoint_path checkpoint-2376 --batch_size 3 --seed 0 --noise_std 0.1
6
+
7
+ CUDA_VISIBLE_DEVICES=2,3 torchrun --nproc_per_node=2 --master_port=25238 gpt2_noise.py --perturbation hop_control --train_set 10M --checkpoint_path checkpoint-2409 --batch_size 3 --seed 0 --noise_std 0.1
perplexities/llama3.2_remove_layers.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import sys
3
+ import argparse
4
+ import os
5
+ sys.path.append("..")
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling
7
+ from datasets import load_dataset
8
+ from numpy.random import default_rng
9
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
10
+
11
+ MODEL_NAME = "Llama-3.2-3B"
12
+ ori_model_name = "meta-llama/Llama-3.2-3B"
13
+ MODEL_NAME_SAVE = "Llama-3.2-3B-500-Remove-layers"
14
+
15
+
16
+ FILE_SAMPLE_SIZE = 500
17
+
18
+
19
+ def remove_layers(original_model, exp_model, i, j):
20
+
21
+ original_layers = original_model.model.layers
22
+ exp_layers = exp_model.model.layers
23
+
24
+ print("len(layers):", len(original_layers))
25
+ # Ensure the indices are valid
26
+ if i < 0 or (i + j) > len(exp_layers) or j < 0:
27
+ raise ValueError(f"Invalid layer indices: i={i}, i+j={i+j}. Must satisfy 0 <= i and i+j <= {len(exp_layers)}.")
28
+
29
+ # Replace the parameters of the layers from i to i+j
30
+ for idx in range(i, i + j):
31
+ print(f"Replacing parameters of LlamaDecoderLayer {idx}...")
32
+ original_layer = original_layers[idx]
33
+ exp_layers[idx].load_state_dict(original_layer.state_dict())
34
+
35
+ return exp_model
36
+
37
+
38
+ def get_perplexities(model, eval_dataset, batch_size):
39
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
40
+
41
+ training_args = TrainingArguments(
42
+ output_dir="./tmp_trainer",
43
+ per_device_eval_batch_size=batch_size,
44
+ fp16=True,
45
+ report_to="none"
46
+ )
47
+
48
+ trainer = Trainer(model=model, args=training_args, eval_dataset=eval_dataset, data_collator=data_collator)
49
+ eval_results = trainer.evaluate()
50
+ print("eval_results:", eval_results)
51
+ loss = eval_results['eval_loss']
52
+ perplexity = torch.exp(torch.tensor(loss)).item()
53
+ return perplexity
54
+
55
+ if __name__ == "__main__":
56
+ parser = argparse.ArgumentParser(description="Calculate perplexity on test dataset.")
57
+
58
+ parser.add_argument('perturbation',
59
+ type=str,
60
+ default='reverse_full',
61
+ nargs='?',
62
+ help='Type of perturbation to use.')
63
+ parser.add_argument('train_set',
64
+ type=str,
65
+ default='test',
66
+ nargs='?',
67
+ help='Dataset size for training.')
68
+ parser.add_argument('checkpoint_path',
69
+ type=str,
70
+ default='checkpoint-100',
71
+ nargs='?',
72
+ help='Dataset size for training.')
73
+ parser.add_argument('batch_size',
74
+ type=int,
75
+ default=4,
76
+ nargs='?',
77
+ help='Batch size for evaluation.')
78
+ parser.add_argument('seed',
79
+ type=int,
80
+ default=0,
81
+ nargs='?',
82
+ help='Random seed.')
83
+ parser.add_argument('remove_layer',
84
+ type=int,
85
+ default=1,
86
+ nargs='?',
87
+ help='Layer index to remove')
88
+ args = parser.parse_args()
89
+
90
+ dataset_name = f"babylm_{args.perturbation}_{args.train_set}_seed{args.seed}"
91
+ dataset = load_dataset('../train/babylm_dataset_test.py', name=dataset_name, trust_remote_code=True)
92
+ test_dataset = dataset['test'] # Load test dataset
93
+ print(test_dataset)
94
+
95
+
96
+ checkpoint_path = f'../train/checkpoints/{MODEL_NAME}/babylm_{args.perturbation}_10M_seed0/runs/{args.checkpoint_path}'
97
+
98
+ rng = default_rng(args.seed)
99
+ indices = rng.choice(len(test_dataset), FILE_SAMPLE_SIZE, replace=False)
100
+ sampled_test_dataset = test_dataset.select(indices)
101
+
102
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
103
+ model = AutoModelForCausalLM.from_pretrained(checkpoint_path)
104
+ model_ori = AutoModelForCausalLM.from_pretrained(ori_model_name)
105
+ model = remove_layers(model_ori, model, args.remove_layer, 9)
106
+ print(model)
107
+ model.eval()
108
+ # if torch.cuda.is_available():
109
+ # model.to('cuda')
110
+
111
+ def tokenize_function(examples):
112
+ return tokenizer(examples['text'], padding="max_length", truncation=True, max_length=1024)
113
+
114
+ tokenized_test = sampled_test_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
115
+
116
+
117
+ perplexity = get_perplexities(model, tokenized_test, args.batch_size)
118
+
119
+ # Save the result to the specified directory
120
+ output_directory = f"perplexities_results/{MODEL_NAME_SAVE}"
121
+ os.makedirs(output_directory, exist_ok=True)
122
+
123
+ # Construct the output file path
124
+ output_file = os.path.join(output_directory, f"{args.perturbation}_{args.batch_size}_{args.seed}.csv")
125
+
126
+ # Write the header to the CSV file if it doesn't exist
127
+ if not os.path.exists(output_file):
128
+ with open(output_file, 'w') as f:
129
+ print("Writing header to CSV...")
130
+ f.write(f"checkpoint_path, perplexity\n")
131
+
132
+ # Append the perplexity result to the CSV file
133
+ with open(output_file, 'a') as f: # Open in append mode
134
+ print("Appending result to CSV...")
135
+ f.write(f"{args.checkpoint_path}-{args.remove_layer+1}, {perplexity}\n")
perplexities/perplexities.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # perplexities.py
2
+ # Author: Julie Kallini
3
+
4
+ # For importing utils
5
+ import sys
6
+ sys.path.append("..")
7
+
8
+ from transformers import GPT2LMHeadModel
9
+ from gpt2_no_positional_encoding_model import GPT2NoPositionalEncodingLMHeadModel
10
+ from utils import CHECKPOINT_READ_PATH, PERTURBATIONS, BABYLM_DATA_PATH, \
11
+ PAREN_MODELS, gpt2_original_tokenizer
12
+ from tqdm import tqdm
13
+ from glob import glob
14
+ from numpy.random import default_rng
15
+ import pandas as pd
16
+ import torch
17
+ import itertools
18
+ import argparse
19
+ import os
20
+
21
+
22
+ MAX_TRAINING_STEPS = 3000
23
+ CHECKPOINTS = list(range(100, MAX_TRAINING_STEPS+1, 100))
24
+
25
+
26
+ def create_attention_mask(token_lists):
27
+ seq_length = max([len(i) for i in token_lists])
28
+ batch_size = len(token_lists)
29
+ mask = torch.full((batch_size, seq_length), 0)
30
+
31
+ for i, tokens in enumerate(token_lists):
32
+ mask[i, 0:len(tokens)] = 1
33
+
34
+ return mask
35
+
36
+ def create_input_ids(token_lists, pad_token_id):
37
+ padded = zip(*itertools.zip_longest(*token_lists, fillvalue=pad_token_id))
38
+ return torch.tensor(list(padded))
39
+
40
+
41
+ def get_perplexities(model, token_lists, pad_token_id, device="cuda"):
42
+
43
+ # Prepare data
44
+ input_ids = create_input_ids(token_lists, pad_token_id).to(device)
45
+ labels = input_ids.clone() # GPT-2 uses input as labels for CLM task
46
+ attention_mask = create_attention_mask(token_lists).to(device)
47
+
48
+ # Forward pass
49
+ outputs = model(input_ids=input_ids, labels=labels,
50
+ attention_mask=attention_mask)
51
+
52
+ # The "shifted" nature of labels in GPT-2 (next token prediction)
53
+ # Shift logits, labels, and attention mask by one position
54
+ shift_logits = outputs.logits[..., :-1, :].contiguous()
55
+ shift_labels = labels[..., 1:].contiguous()
56
+ shift_attention_mask = attention_mask[..., 1:].contiguous()
57
+
58
+ # Instantiate loss function with no reduction
59
+ loss_fct = torch.nn.CrossEntropyLoss(reduction='none')
60
+
61
+ # Calculate per-token loss
62
+ loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)),
63
+ shift_labels.view(-1))
64
+
65
+ # Reshape back to the original batch size and sequence length
66
+ loss = loss.view(shift_labels.size())
67
+
68
+ # Apply the attention mask - only calculate loss where mask is 1
69
+ loss = loss * shift_attention_mask
70
+
71
+ # Sum the loss over the sequence length, get per-example perplexity
72
+ per_example_loss = loss.sum(dim=1) / shift_attention_mask.sum(dim=1)
73
+ return torch.exp(per_example_loss).tolist()
74
+
75
+
76
+ if __name__ == "__main__":
77
+
78
+ parser = argparse.ArgumentParser(
79
+ prog='Edge probing',
80
+ description='Edge probing experiments')
81
+ parser.add_argument('perturbation_type',
82
+ default='all',
83
+ const='all',
84
+ nargs='?',
85
+ choices=PERTURBATIONS.keys(),
86
+ help='Perturbation function used to transform BabyLM dataset')
87
+ parser.add_argument('test_perturbation_type',
88
+ default='all',
89
+ const='all',
90
+ nargs='?',
91
+ choices=PERTURBATIONS.keys(),
92
+ help='Perturbation function used to transform test BabyLM dataset')
93
+ parser.add_argument('train_set',
94
+ default='all',
95
+ const='all',
96
+ nargs='?',
97
+ choices=["100M", "10M"],
98
+ help='BabyLM train set')
99
+ parser.add_argument('random_seed', type=int, help="Random seed")
100
+ parser.add_argument('paren_model',
101
+ default='all',
102
+ const='all',
103
+ nargs='?',
104
+ choices=list(PAREN_MODELS.keys()) + ["randinit"],
105
+ help='Parenthesis model')
106
+ parser.add_argument('-np', '--no_pos_encodings', action='store_true',
107
+ help="Train GPT-2 with no positional encodings")
108
+
109
+ # Get args
110
+ args = parser.parse_args()
111
+ no_pos_encodings_underscore = "_no_positional_encodings" if args.no_pos_encodings else ""
112
+
113
+ # Get path to model
114
+ model = f"babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}_seed{args.random_seed}"
115
+ model_path = f"{CHECKPOINT_READ_PATH}/babylm_{args.perturbation_type}_{args.train_set}_{args.paren_model}{no_pos_encodings_underscore}/{model}/runs/{model}/checkpoint-"
116
+
117
+ # Get perturbed test files
118
+ test_files = sorted(glob(
119
+ f"{BABYLM_DATA_PATH}/babylm_data_perturbed/babylm_{args.test_perturbation_type}/babylm_test_affected/*"))
120
+
121
+ FILE_SAMPLE_SIZE = 1000
122
+ rng = default_rng(args.random_seed)
123
+
124
+ # Iterate over data files to get perplexity data
125
+ print("Sampling BabyLM affected test files to extract surprisals...")
126
+ token_sequences = []
127
+ for test_file in test_files:
128
+ print(test_file)
129
+
130
+ # Get tokens from test file and subsample
131
+ f = open(test_file, 'r')
132
+ file_token_sequences = [
133
+ [int(s) for s in l.split()] for l in f.readlines()]
134
+ sample_indices = rng.choice(
135
+ list(range(len(file_token_sequences))), FILE_SAMPLE_SIZE, replace=False)
136
+ file_token_sequences = [file_token_sequences[i]
137
+ for i in sample_indices]
138
+ token_sequences.extend(file_token_sequences)
139
+
140
+ # For logging/debugging, include decoded sentence
141
+ test_sents = [gpt2_original_tokenizer.decode(
142
+ toks) for toks in token_sequences]
143
+
144
+ ppl_df = pd.DataFrame({
145
+ "Sentences": test_sents
146
+ })
147
+
148
+ BATCH_SIZE = 8
149
+ device = "cuda"
150
+ for ckpt in CHECKPOINTS:
151
+ print(f"Checkpoint: {ckpt}")
152
+
153
+ # Load model
154
+ if args.no_pos_encodings:
155
+ model = GPT2NoPositionalEncodingLMHeadModel.from_pretrained(
156
+ model_path + str(ckpt)).to(device)
157
+ else:
158
+ model = GPT2LMHeadModel.from_pretrained(
159
+ model_path + str(ckpt)).to(device)
160
+
161
+ # Get perplexities
162
+ perplexities = []
163
+ for i in tqdm(range(0, len(token_sequences), BATCH_SIZE)):
164
+ batch = token_sequences[i:i+BATCH_SIZE]
165
+ ppls = get_perplexities(
166
+ model, batch, gpt2_original_tokenizer.eos_token_id)
167
+ perplexities.extend(ppls)
168
+
169
+ # Add ppls to df
170
+ ppl_df[f'Perplexities (ckpt {ckpt})'] = perplexities
171
+
172
+ # Write results to CSV
173
+ directory = f"perplexity_results/{args.perturbation_type}_{args.train_set}{no_pos_encodings_underscore}"
174
+ if not os.path.exists(directory):
175
+ os.makedirs(directory)
176
+ file = directory + \
177
+ f"/{args.paren_model}_seed{args.random_seed}_test_{args.test_perturbation_type}.csv"
178
+ print(f"Writing results to CSV: {file}")
179
+ ppl_df.to_csv(file)
perplexities/perplexities_llama.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import sys
3
+ import argparse
4
+ import os
5
+ sys.path.append("..")
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling
7
+ from datasets import load_dataset
8
+ from numpy.random import default_rng
9
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
10
+
11
+ # MODEL_NAME = "Llama-3.2-1B"
12
+ # MODEL_NAME = "Llama-3.2-1B-500"
13
+
14
+ MODEL_NAME = "GPT-2"
15
+ MODEL_NAME_SAVE = "GPT2-500"
16
+ FILE_SAMPLE_SIZE = 500
17
+
18
+ def get_perplexities(model, eval_dataset, batch_size):
19
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
20
+
21
+ training_args = TrainingArguments(
22
+ output_dir="./tmp_trainer",
23
+ per_device_eval_batch_size=batch_size,
24
+ fp16=True,
25
+ report_to="none"
26
+ )
27
+
28
+ trainer = Trainer(model=model, args=training_args, eval_dataset=eval_dataset, data_collator=data_collator)
29
+ eval_results = trainer.evaluate()
30
+ print("eval_results:", eval_results)
31
+ loss = eval_results['eval_loss']
32
+ perplexity = torch.exp(torch.tensor(loss)).item()
33
+ return perplexity
34
+
35
+ if __name__ == "__main__":
36
+ parser = argparse.ArgumentParser(description="Calculate perplexity on test dataset.")
37
+
38
+ parser.add_argument('perturbation',
39
+ type=str,
40
+ default='reverse_full',
41
+ nargs='?',
42
+ help='Type of perturbation to use.')
43
+ parser.add_argument('train_set',
44
+ type=str,
45
+ default='test',
46
+ nargs='?',
47
+ help='Dataset size for training.')
48
+ parser.add_argument('checkpoint_path',
49
+ type=str,
50
+ default='checkpoint-100',
51
+ nargs='?',
52
+ help='Dataset size for training.')
53
+ parser.add_argument('batch_size',
54
+ type=int,
55
+ default=4,
56
+ nargs='?',
57
+ help='Batch size for evaluation.')
58
+ parser.add_argument('seed',
59
+ type=int,
60
+ default=0,
61
+ nargs='?',
62
+ help='Random seed.')
63
+ args = parser.parse_args()
64
+
65
+ dataset_name = f"babylm_{args.perturbation}_{args.train_set}_seed{args.seed}"
66
+ dataset = load_dataset('../train/babylm_dataset_test.py', name=dataset_name, trust_remote_code=True)
67
+ test_dataset = dataset['test'] # Load test dataset
68
+ print(test_dataset)
69
+
70
+
71
+ checkpoint_path = f'../train/checkpoints/{MODEL_NAME}/babylm_{args.perturbation}_10M_seed0/runs/{args.checkpoint_path}'
72
+
73
+ rng = default_rng(args.seed)
74
+ indices = rng.choice(len(test_dataset), FILE_SAMPLE_SIZE, replace=False)
75
+ sampled_test_dataset = test_dataset.select(indices)
76
+
77
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint_path)
78
+ model = AutoModelForCausalLM.from_pretrained(checkpoint_path)
79
+
80
+ model.eval()
81
+ if torch.cuda.is_available():
82
+ model.to('cuda')
83
+
84
+ def tokenize_function(examples):
85
+ return tokenizer(examples['text'], padding="max_length", truncation=True, max_length=1024)
86
+
87
+ tokenized_test = sampled_test_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
88
+
89
+
90
+ perplexity = get_perplexities(model, tokenized_test, args.batch_size)
91
+
92
+ # Save the result to the specified directory
93
+ output_directory = f"perplexities_results/{MODEL_NAME_SAVE}"
94
+ os.makedirs(output_directory, exist_ok=True)
95
+
96
+ # Construct the output file path
97
+ output_file = os.path.join(output_directory, f"{args.perturbation}_{args.batch_size}_{args.seed}.csv")
98
+
99
+ # Write the header to the CSV file if it doesn't exist
100
+ if not os.path.exists(output_file):
101
+ with open(output_file, 'w') as f:
102
+ print("Writing header to CSV...")
103
+ f.write("checkpoint_path, perplexity\n")
104
+
105
+ # Append the perplexity result to the CSV file
106
+ with open(output_file, 'a') as f: # Open in append mode
107
+ print("Appending result to CSV...")
108
+ f.write(f"{args.checkpoint_path}, {perplexity}\n")
requirements.txt ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ certifi==2023.11.17
2
+ charset-normalizer==3.3.2
3
+ click==8.1.7
4
+ contourpy==1.1.1
5
+ cycler==0.12.1
6
+ data==0.4
7
+ emoji==2.8.0
8
+ fonttools==4.45.1
9
+ fsspec==2023.10.0
10
+ funcsigs==1.0.2
11
+ future==0.18.3
12
+ huggingface-hub==0.19.4
13
+ idna==3.6
14
+ iniconfig==2.0.0
15
+ joblib==1.3.2
16
+ kiwisolver==1.4.5
17
+ latex==0.7.0
18
+ matplotlib==3.7.5
19
+ mizani==0.9.3
20
+ nltk==3.8.1
21
+ numpy==1.24.4
22
+ pandas==2.0.3
23
+ patsy==0.5.3
24
+ Pillow==10.1.0
25
+ plotnine==0.12.4
26
+ pluggy==1.3.0
27
+ pluralizer==1.2.0
28
+ protobuf==4.25.1
29
+ pyparsing==3.1.1
30
+ pytest==7.4.3
31
+ pytz==2023.3.post1
32
+ PyYAML==6.0.1
33
+ regex==2023.10.3
34
+ requests==2.31.0
35
+ safetensors==0.4.1
36
+ scikit-learn==1.3.2
37
+ scipy==1.10.1
38
+ seaborn==0.13.0
39
+ shutilwhich==1.1.0
40
+ stanza==1.6.1
41
+ statsmodels==0.14.0
42
+ tempdir==0.7.1
43
+ threadpoolctl==3.2.0
44
+ tokenizers==0.15.0
45
+ tomli==2.0.1
46
+ torch==2.0.0
47
+ tqdm==4.66.1
48
+ transformers==4.35.2
49
+ triton==2.0.0
50
+ tzdata==2023.3
51
+ urllib3==2.1.0
52
+ asttokens==2.0.5
53
+ comm==0.1.2
54
+ debugpy==1.6.7
55
+ decorator==5.1.1
56
+ exceptiongroup==1.1.0
57
+ executing==1.2.0
58
+ filelock==3.12.2
59
+ gmpy2==2.1.0
60
+ importlib-metadata==6.0.0
61
+ ipykernel==6.23.1
62
+ ipython==8.0.0
63
+ jedi==0.18.2
64
+ Jinja2==3.1.2
65
+ jupyter_client==8.1.0
66
+ jupyter_core==5.1.0
67
+ latexcodec==1.0.0
68
+ MarkupSafe==2.1.2
69
+ matplotlib-inline==0.1.6
70
+ mpmath==1.2.1
71
+ nest-asyncio==1.5.6
72
+ networkx==2.8.6
73
+ packaging==23.0
74
+ parso==0.8.3
75
+ pexpect==4.8.0
76
+ pickleshare==0.7.5
77
+ platformdirs==2.5.2
78
+ prompt-toolkit==3.0.30
79
+ psutil==5.9.1
80
+ ptyprocess==0.7.0
81
+ pure-eval==0.2.2
82
+ Pygments==2.15.0
83
+ python-dateutil==2.8.2
84
+ pyzmq==23.0.0
85
+ six==1.16.0
86
+ stack-data==0.6.0
87
+ sympy==1.11.1
88
+ tornado==6.2
89
+ traitlets==5.7.1
90
+ typing_extensions==4.6.0
91
+ wcwidth==0.2.5
92
+ zipp==3.12.0
93
+
94
+
requirements_1.txt ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ certifi==2023.11.17
2
+ charset-normalizer==3.3.2
3
+ click==8.1.7
4
+ contourpy==1.2.0
5
+ cycler==0.12.1
6
+ data==0.4
7
+ emoji==2.8.0
8
+ fonttools==4.45.1
9
+ fsspec==2023.10.0
10
+ funcsigs==1.0.2
11
+ future==0.18.3
12
+ huggingface-hub==0.19.4
13
+ idna==3.6
14
+ iniconfig==2.0.0
15
+ joblib==1.3.2
16
+ kiwisolver==1.4.5
17
+ latex==0.7.0
18
+ matplotlib==3.8.2
19
+ mizani==0.9.3
20
+ nltk==3.8.1
21
+ numpy==1.26.2
22
+ pandas==2.1.3
23
+ patsy==0.5.3
24
+ Pillow==10.1.0
25
+ plotnine==0.12.4
26
+ pluggy==1.3.0
27
+ pluralizer==1.2.0
28
+ protobuf==4.25.1
29
+ pyparsing==3.1.1
30
+ pytest==7.4.3
31
+ pytz==2023.3.post1
32
+ PyYAML==6.0.1
33
+ regex==2023.10.3
34
+ requests==2.31.0
35
+ safetensors==0.4.1
36
+ scikit-learn==1.3.2
37
+ scipy==1.11.4
38
+ seaborn==0.13.0
39
+ shutilwhich==1.1.0
40
+ stanza==1.6.1
41
+ statsmodels==0.14.0
42
+ tempdir==0.7.1
43
+ threadpoolctl==3.2.0
44
+ tokenizers==0.15.0
45
+ tomli==2.0.1
46
+ torch==2.0.0
47
+ tqdm==4.66.1
48
+ transformers==4.35.2
49
+ triton==2.0.0
50
+ tzdata==2023.3
51
+ urllib3==2.1.0