mirror of
https://github.com/wassname/discovering_latent_knowledge.git
synced 2026-09-21 12:50:41 +08:00
20 KiB
20 KiB
In [1]:
from tqdm import tqdm
import copy
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, AutoModelForMaskedLM, AutoModelForCausalLM
from sklearn.linear_model import LogisticRegression
# Let's just try IMDB for simplicity
data = load_dataset("amazon_polarity")["test"]
# Here are a few different model options you can play around with:
model_name = "deberta"
# model_name = "gpt-j"
# model_name = "t5"
# if you want to cache the model weights somewhere, you can specify that here
cache_dir = None
if model_name == "deberta":
model_type = "encoder"
tokenizer = AutoTokenizer.from_pretrained("microsoft/deberta-v2-xxlarge", cache_dir=cache_dir)
model = AutoModelForMaskedLM.from_pretrained("microsoft/deberta-v2-xxlarge", cache_dir=cache_dir)
model.cuda()
elif model_name == "gpt-j":
model_type = "decoder"
tokenizer = AutoTokenizer.from_pretrained("EleutherAI/gpt-j-6B", cache_dir=cache_dir)
model = AutoModelForCausalLM.from_pretrained("EleutherAI/gpt-j-6B", cache_dir=cache_dir)
model.cuda()
elif model_name == "t5":
model_type = "encoder_decoder"
tokenizer = AutoTokenizer.from_pretrained("t5-11b", cache_dir=cache_dir)
model = AutoModelForSeq2SeqLM.from_pretrained("t5-11b", cache_dir=cache_dir)
model.parallelize() # T5 is big enough that we may need to run it on multiple GPUs
else:
print("Not implemented!")/accounts/projects/jsteinhardt/uid1693600/.local/lib/python3.7/site-packages/pandas/compat/_optional.py:138: UserWarning: Pandas requires version '2.7.0' or newer of 'numexpr' (version '2.6.9' currently installed). warnings.warn(msg, UserWarning) W1206 14:15:47.056510 140146004121408 builder.py:641] Reusing dataset amazon_polarity (/scratch/users/uid1693600/huggingface-cache/amazon_polarity/amazon_polarity/3.0.0/a27b32b7e7b88eb274a8fa8ba0f654f1fe998a87c22547557317793b5d2772dc)
0%| | 0/2 [00:00<?, ?it/s]
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained. Some weights of the model checkpoint at microsoft/deberta-v2-xxlarge were not used when initializing DebertaV2ForMaskedLM: ['lm_predictions.lm_head.bias', 'lm_predictions.lm_head.LayerNorm.bias', 'lm_predictions.lm_head.dense.bias', 'lm_predictions.lm_head.dense.weight', 'lm_predictions.lm_head.LayerNorm.weight', 'deberta.embeddings.position_embeddings.weight'] - This IS expected if you are initializing DebertaV2ForMaskedLM from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model). - This IS NOT expected if you are initializing DebertaV2ForMaskedLM from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model). Some weights of DebertaV2ForMaskedLM were not initialized from the model checkpoint at microsoft/deberta-v2-xxlarge and are newly initialized: ['cls.predictions.decoder.weight', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.dense.weight', 'cls.predictions.transform.dense.bias', 'cls.predictions.bias', 'cls.predictions.transform.LayerNorm.bias'] You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
In [2]:
def get_encoder_hidden_states(model, tokenizer, input_text, layer=-1):
"""
Given an encoder model and some text, gets the encoder hidden states (in a given layer, by default the last)
on that input text (where the full text is given to the encoder).
Returns a numpy array of shape (hidden_dim,)
"""
# tokenize
encoder_text_ids = tokenizer(input_text, truncation=True, return_tensors="pt").input_ids.to(model.device)
# forward pass
with torch.no_grad():
output = model(encoder_text_ids, output_hidden_states=True)
# get the appropriate hidden states
hs_tuple = output["hidden_states"]
hs = hs_tuple[layer][0, -1].detach().cpu().numpy()
return hs
def get_encoder_decoder_hidden_states(model, tokenizer, input_text, layer=-1):
"""
Given an encoder-decoder model and some text, gets the encoder hidden states (in a given layer, by default the last)
on that input text (where the full text is given to the encoder).
Returns a numpy array of shape (hidden_dim,)
"""
# tokenize
encoder_text_ids = tokenizer(input_text, return_tensors="pt").input_ids.to(model.device)
decoder_text_ids = tokenizer("", return_tensors="pt").input_ids.to(model.device)
# forward pass
with torch.no_grad():
output = model(encoder_text_ids, decoder_input_ids=decoder_text_ids, output_hidden_states=True)
# get the appropriate hidden states
hs_tuple = output["encoder_hidden_states"]
hs = hs_tuple[layer][0, -1].detach().cpu().numpy()
return hs
def get_decoder_hidden_states(model, tokenizer, input_text, layer=-1):
"""
Given a decoder model and some text, gets the hidden states (in a given layer, by default the last) on that input text
Returns a numpy array of shape (hidden_dim,)
"""
# tokenize (adding the EOS token this time)
input_ids = tokenizer(input_text + tokenizer.eos_token, return_tensors="pt").input_ids.to(model.device)
# forward pass
with torch.no_grad():
output = model(input_ids, output_hidden_states=True)
# get the last layer, last token hidden states
hs_tuple = output["hidden_states"]
hs = hs_tuple[layer][0, -1].detach().cpu().numpy()
return hs
def get_hidden_states(model, tokenizer, input_text, layer=-1, model_type="encoder"):
fn = {"encoder": get_encoder_hidden_states, "encoder_decoder": get_encoder_decoder_hidden_states,
"decoder": get_decoder_hidden_states}[model_type]
return fn(model, tokenizer, input_text, layer=layer)In [3]:
def format_imdb(text, label):
"""
Given an imdb example ("text") and corresponding label (0 for negative, or 1 for positive),
returns a zero-shot prompt for that example (which includes that label as the answer).
(This is just one example of a simple, manually created prompt.)
"""
return "The following movie review expresses a " + ["negative", "positive"][label] + " sentiment:\n" + text
def get_hidden_states_many_examples(model, tokenizer, data, model_type, n=100):
"""
Given an encoder-decoder model, a list of data, computes the contrast hidden states on n random examples.
Returns numpy arrays of shape (n, hidden_dim) for each candidate label, along with a boolean numpy array of shape (n,)
with the ground truth labels
This is deliberately simple so that it's easy to understand, rather than being optimized for efficiency
"""
# setup
model.eval()
all_neg_hs, all_pos_hs, all_gt_labels = [], [], []
# loop
for _ in tqdm(range(n)):
# for simplicity, sample a random example until we find one that's a reasonable length
# (most examples should be a reasonable length, so this is just to make sure)
while True:
idx = np.random.randint(len(data))
text, true_label = data[idx]["content"], data[idx]["label"]
# the actual formatted input will be longer, so include a bit of a marign
if len(tokenizer(text)) < 400:
break
# get hidden states
neg_hs = get_hidden_states(model, tokenizer, format_imdb(text, 0), model_type=model_type)
pos_hs = get_hidden_states(model, tokenizer, format_imdb(text, 1), model_type=model_type)
# collect
all_neg_hs.append(neg_hs)
all_pos_hs.append(pos_hs)
all_gt_labels.append(true_label)
all_neg_hs = np.stack(all_neg_hs)
all_pos_hs = np.stack(all_pos_hs)
all_gt_labels = np.stack(all_gt_labels)
return all_neg_hs, all_pos_hs, all_gt_labelsIn [4]:
neg_hs, pos_hs, y = get_hidden_states_many_examples(model, tokenizer, data, model_type)100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 100/100 [00:19<00:00, 5.10it/s]
In [5]:
# let's create a simple 50/50 train split (the data is already randomized)
n = len(y)
neg_hs_train, neg_hs_test = neg_hs[:n//2], neg_hs[n//2:]
pos_hs_train, pos_hs_test = pos_hs[:n//2], pos_hs[n//2:]
y_train, y_test = y[:n//2], y[n//2:]
# for simplicity we can just take the difference between positive and negative hidden states
# (concatenating also works fine)
x_train = neg_hs_train - pos_hs_train
x_test = neg_hs_test - pos_hs_test
lr = LogisticRegression(class_weight="balanced")
lr.fit(x_train, y_train)
print("Logistic regression accuracy: {}".format(lr.score(x_test, y_test)))Logistic regression accuracy: 0.9
In [6]:
class MLPProbe(nn.Module):
def __init__(self, d):
super().__init__()
self.linear1 = nn.Linear(d, 100)
self.linear2 = nn.Linear(100, 1)
def forward(self, x):
h = F.relu(self.linear1(x))
o = self.linear2(h)
return torch.sigmoid(o)
class CCS(object):
def __init__(self, x0, x1, nepochs=1000, ntries=10, lr=1e-3, batch_size=-1,
verbose=False, device="cuda", linear=True, weight_decay=0.01, var_normalize=False):
# data
self.var_normalize = var_normalize
self.x0 = self.normalize(x0)
self.x1 = self.normalize(x1)
self.d = self.x0.shape[-1]
# training
self.nepochs = nepochs
self.ntries = ntries
self.lr = lr
self.verbose = verbose
self.device = device
self.batch_size = batch_size
self.weight_decay = weight_decay
# probe
self.linear = linear
self.probe = self.initialize_probe()
self.best_probe = copy.deepcopy(self.probe)
def initialize_probe(self):
if self.linear:
self.probe = nn.Linear(self.d, 1)
else:
self.probe = MLPProbe(self.d)
self.probe.to(self.device)
def normalize(self, x):
"""
Mean-normalizes the data x (of shape (n, d))
If self.var_normalize, also divides by the standard deviation
"""
normalized_x = x - x.mean(axis=0, keepdims=True)
if self.var_normalize:
normalized_x /= normalized_x.std(axis=0, keepdims=True)
return normalized_x
def get_tensor_data(self):
"""
Returns x0, x1 as appropriate tensors (rather than np arrays)
"""
x0 = torch.tensor(self.x0, dtype=torch.float, requires_grad=False, device=self.device)
x1 = torch.tensor(self.x1, dtype=torch.float, requires_grad=False, device=self.device)
return x0, x1
def get_loss(self, p0, p1):
"""
Returns the CCS loss for two probabilities each of shape (n,1) or (n,)
"""
informative_loss = (torch.min(p0, p1)**2).mean(0)
consistent_loss = ((p0 - (1-p1))**2).mean(0)
return informative_loss + consistent_loss
def get_acc(self, x0_test, x1_test, y_test):
"""
Computes accuracy for the current parameters on the given test inputs
"""
x0 = torch.tensor(self.normalize(x0_test), dtype=torch.float, requires_grad=False, device=self.device)
x1 = torch.tensor(self.normalize(x1_test), dtype=torch.float, requires_grad=False, device=self.device)
with torch.no_grad():
p0, p1 = self.best_probe(x0), self.best_probe(x1)
avg_confidence = 0.5*(p0 + (1-p1))
predictions = (avg_confidence.detach().cpu().numpy() < 0.5).astype(int)[:, 0]
acc = (predictions == y_test).mean()
acc = max(acc, 1 - acc)
return acc
def train(self):
"""
Does a single training run of nepochs epochs
"""
x0, x1 = self.get_tensor_data()
permutation = torch.randperm(len(x0))
x0, x1 = x0[permutation], x1[permutation]
# set up optimizer
optimizer = torch.optim.AdamW(self.probe.parameters(), lr=self.lr, weight_decay=self.weight_decay)
batch_size = len(x0) if self.batch_size == -1 else self.batch_size
nbatches = len(x0) // batch_size
# Start training (full batch)
for epoch in range(self.nepochs):
for j in range(nbatches):
x0_batch = x0[j*batch_size:(j+1)*batch_size]
x1_batch = x1[j*batch_size:(j+1)*batch_size]
# probe
p0, p1 = self.probe(x0_batch), self.probe(x1_batch)
# get the corresponding loss
loss = self.get_loss(p0, p1)
# update the parameters
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.detach().cpu().item()
def repeated_train(self):
best_loss = np.inf
for train_num in range(self.ntries):
self.initialize_probe()
loss = self.train()
if loss < best_loss:
self.best_probe = copy.deepcopy(self.probe)
best_loss = loss
return best_lossIn [7]:
# Train CCS without any labels
ccs = CCS(neg_hs_train, pos_hs_train)
ccs.repeated_train()
# Evaluate
ccs_acc = ccs.get_acc(neg_hs_test, pos_hs_test, y_test)
print("CCS accuracy: {}".format(ccs_acc))CCS accuracy: 0.88