sandeshrajx's picture
download
raw
10.6 kB
import os
import sys
import math
import random
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from transformers import GPT2Config, GPT2LMHeadModel
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import f1_score
from tqdm import tqdm
# Set seeds
torch.manual_seed(42)
np.random.seed(42)
random.seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}", flush=True)
# Helper to count parameters
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
class SyntheticDataset(Dataset):
def __init__(self, num_samples, seq_len, vocab_size):
self.data = torch.randint(0, vocab_size, (num_samples, seq_len))
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx]
def train_model(model, train_loader, epochs=150, lr=2e-3, desc=""):
model.train()
optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=0.01)
for epoch in range(epochs):
total_loss = 0
for batch in train_loader:
if isinstance(batch, (list, tuple)):
batch = batch[0]
batch = batch.to(device)
outputs = model(batch, labels=batch)
loss = outputs.loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
if (epoch + 1) % 25 == 0 or epoch == 0:
print(f" [{desc}] Epoch {epoch+1:3d}/{epochs}: Loss = {avg_loss:.6f}", flush=True)
# Early stopping if fully overfitted (loss < 1e-4)
if avg_loss < 1e-4:
print(f" [{desc}] Early stopping at epoch {epoch+1} (Loss = {avg_loss:.6f})", flush=True)
break
return model
def compute_dataset_entropy(N, S, V):
return N * S * math.log2(V)
def compute_model_description_length(model, data_loader):
model.eval()
total_nll_bits = 0.0
with torch.no_grad():
for batch in data_loader:
if isinstance(batch, (list, tuple)):
batch = batch[0]
batch = batch.to(device)
outputs = model(batch, labels=batch)
num_elements = batch.numel()
nll_bits = (outputs.loss.item() * num_elements) / math.log(2.0)
total_nll_bits += nll_bits
return total_nll_bits
def run_synthetic_experiments():
print("\n--- Running Claim 1 (Synthetic Capacity) ---", flush=True)
vocab_size = 2048
seq_len = 64
# 2 layers, 2 heads, 64 embedding size
config = GPT2Config(
vocab_size=vocab_size,
n_positions=seq_len,
n_ctx=seq_len,
n_embd=64,
n_layer=2,
n_head=2,
bos_token_id=0,
eos_token_id=0
)
model = GPT2LMHeadModel(config)
num_params = count_parameters(model)
print(f"Model parameters: {num_params}", flush=True)
dataset_sizes = [50, 200, 500, 1000, 1500, 2000, 2500]
memorization_results = []
for N in dataset_sizes:
print(f"\n[Claim 1] Training on dataset size N={N}...", flush=True)
dataset = SyntheticDataset(N, seq_len, vocab_size)
loader = DataLoader(dataset, batch_size=64, shuffle=True)
m = GPT2LMHeadModel(config).to(device)
m = train_model(m, loader, epochs=150, lr=2e-3, desc=f"Synth N={N}")
H_X = compute_dataset_entropy(N, seq_len, vocab_size)
eval_loader = DataLoader(dataset, batch_size=64, shuffle=False)
H_K_X_given_theta = compute_model_description_length(m, eval_loader)
mem = H_X - H_K_X_given_theta
mem = max(0.0, mem)
bpp = mem / num_params
memorization_results.append((N, mem, bpp))
print(f" [Claim 1] N={N} | Entropy H(X): {H_X:.1f} bits | NLL H^K: {H_K_X_given_theta:.1f} bits", flush=True)
print(f" [Claim 1] N={N} | Memorized: {mem:.1f} bits ({bpp:.4f} bits/param)", flush=True)
max_mem_entry = max(memorization_results, key=lambda x: x[1])
capacity = max_mem_entry[1]
alpha = max_mem_entry[2]
print(f"\n[Claim 1] Estimated Model Capacity: {capacity:.1f} bits", flush=True)
print(f"[Claim 1] Estimated alpha (bits per parameter): {alpha:.4f}", flush=True)
Ns = [r[0] for r in memorization_results]
mems = [r[1] for r in memorization_results]
bpps = [r[2] for r in memorization_results]
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(Ns, mems, 'o-')
plt.xlabel('Dataset Size (N)')
plt.ylabel('Memorized Bits')
plt.title('Total Memorization')
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(Ns, bpps, 'o-', color='orange')
plt.axhline(y=alpha, color='r', linestyle='--', label=f'Alpha: {alpha:.3f}')
plt.xlabel('Dataset Size (N)')
plt.ylabel('Bits Per Parameter (bpp)')
plt.title('Memorization Capacity (bpp)')
plt.legend()
plt.grid(True)
os.makedirs('outputs', exist_ok=True)
plt.savefig('outputs/synthetic_capacity.png')
plt.close()
return num_params, capacity, alpha
def run_text_experiments(num_params, capacity):
print("\n--- Running Claim 2 (Double Descent) and Claim 3 (Membership Inference) ---", flush=True)
words = ["the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog", "model", "parameter", "memorize",
"generalize", "capacity", "data", "training", "entropy", "compress", "information", "theory",
"double", "descent", "grokking", "sigmoid", "scaling", "law", "inference", "privacy", "copyright"]
for i in range(972):
words.append(f"word_{i}")
vocab_size = len(words)
seq_len = 64
def generate_text_sample(length):
sentence = []
for _ in range(length):
if random.random() < 0.2:
sentence.append(random.choice([0, 1, 2, 3]))
else:
sentence.append(random.randint(0, vocab_size-1))
return torch.tensor(sentence)
N_sizes = [50, 200, 500, 1000, 1500, 2000, 3000]
test_size = 200
test_data = torch.stack([generate_text_sample(seq_len) for _ in range(test_size)])
test_dataset = torch.utils.data.TensorDataset(test_data)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
config = GPT2Config(
vocab_size=vocab_size,
n_positions=seq_len,
n_ctx=seq_len,
n_embd=64,
n_layer=2,
n_head=2,
bos_token_id=0,
eos_token_id=0
)
train_losses = []
test_losses = []
f1_scores = []
for N in N_sizes:
print(f"\n[Claim 2&3] Training on text dataset size N={N}...", flush=True)
train_data = torch.stack([generate_text_sample(seq_len) for _ in range(N)])
train_dataset = torch.utils.data.TensorDataset(train_data)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
m = GPT2LMHeadModel(config).to(device)
m = train_model(m, train_loader, epochs=150, lr=2e-3, desc=f"Text N={N}")
m.eval()
train_loss = 0.0
with torch.no_grad():
for batch in train_loader:
batch = batch[0].to(device)
outputs = m(batch, labels=batch)
train_loss += outputs.loss.item()
train_loss /= len(train_loader)
train_losses.append(train_loss)
test_loss = 0.0
with torch.no_grad():
for batch in test_loader:
batch = batch[0].to(device)
outputs = m(batch, labels=batch)
test_loss += outputs.loss.item()
test_loss /= len(test_loader)
test_losses.append(test_loss)
train_losses_individual = []
with torch.no_grad():
for i in range(N):
sample = train_data[i:i+1].to(device)
loss = m(sample, labels=sample).loss.item()
train_losses_individual.append(loss)
test_losses_individual = []
with torch.no_grad():
for i in range(test_size):
sample = test_data[i:i+1].to(device)
loss = m(sample, labels=sample).loss.item()
test_losses_individual.append(loss)
all_losses = np.array(train_losses_individual + test_losses_individual)
labels = np.array([1]*N + [0]*test_size)
best_f1 = 0.0
thresholds = np.percentile(all_losses, np.linspace(0, 100, 100))
for t in thresholds:
preds = (all_losses < t).astype(int)
f1 = f1_score(labels, preds)
if f1 > best_f1:
best_f1 = f1
f1_scores.append(best_f1)
print(f" [Claim 2&3] N={N} | Train Loss: {train_loss:.4f} | Test Loss: {test_loss:.4f} | Membership F1: {best_f1:.4f}", flush=True)
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(N_sizes, train_losses, 'o-', label='Train Loss')
plt.plot(N_sizes, test_losses, 'o-', label='Test Loss')
plt.axvline(x=capacity / (seq_len * math.log2(vocab_size)), color='r', linestyle='--', label='Est. Capacity')
plt.xlabel('Dataset Size (N)')
plt.ylabel('Loss')
plt.title('Train/Test Losses vs Dataset Size')
plt.legend()
plt.grid(True)
c1, c2, c3 = 1.34, -0.034, -33.14
pred_f1s = []
for N in N_sizes:
ratio = capacity / N
sig = 1.0 / (1.0 + math.exp(-(c2 * (ratio + c3))))
pred = 0.5 * (1.0 + c1 * sig)
pred_f1s.append(pred)
plt.subplot(1, 2, 2)
plt.plot(N_sizes, f1_scores, 'o-', label='Empirical F1')
plt.plot(N_sizes, pred_f1s, 'x--', label='Paper Scaling Law')
plt.xlabel('Dataset Size (N)')
plt.ylabel('Membership F1')
plt.title('Membership Inference F1')
plt.legend()
plt.grid(True)
plt.savefig('outputs/text_results.png')
plt.close()
print("\nScaling Law verification results:", flush=True)
for N, emp, pred in zip(N_sizes, f1_scores, pred_f1s):
print(f" N={N:4d} | Empirical F1: {emp:.4f} | Predicted F1: {pred:.4f} | Diff: {abs(emp - pred):.4f}", flush=True)
if __name__ == "__main__":
num_params, capacity, alpha = run_synthetic_experiments()
run_text_experiments(num_params, capacity)

Xet Storage Details

Size:
10.6 kB
·
Xet hash:
85c3ad5991a53549a01d68289b319f0d0b46c47f8d9179467865f7d358a002eb

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.