NLP and transformers / BERT and language models
Introduction to Context Evolution in Language Models
Contents
- Introduction to Context Evolution in Language Models
- Understanding Context in Language Models
- Implementing a Basic Context Window
- Implementing Attention Mechanism
- Implementing Token-level Context Evolution
- Implementing Sentence-level Context Evolution
- Implementing Dynamic Context Windows
- Implementing Context-aware Attention
- Implementing Hierarchical Context Evolution
- Implementing Context-aware Language Model
- Implementing Context-based Text Generation
- Evaluating Context Evolution
- Fine-tuning for Context Evolution
- Additional Resources
Introduction to Context Evolution in Language Models
Context evolution in language models refers to the dynamic process of updating and adapting the contextual understanding of a model as it processes sequential input. This concept is crucial for improving the performance and relevance of language models in various natural language processing tasks.
import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer
model = GPT2LMHeadModel.from_pretrained('gpt2')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
text = "The quick brown fox"
input_ids = tokenizer.encode(text, return_tensors='pt')
output = model.generate(input_ids, max_length=50, num_return_sequences=1)
generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(generated_text)
Understanding Context in Language Models
Context in language models represents the surrounding information that influences the interpretation of words or phrases. It includes previous words, sentences, or even entire documents that help the model understand and generate coherent text.
def simple_context_model(text, context_size):
words = text.split()
context = []
for i in range(len(words)):
start = max(0, i - context_size)
context.append(words[start:i])
return context
text = "The quick brown fox jumps over the lazy dog"
context = simple_context_model(text, 3)
print(context)
Implementing a Basic Context Window
A context window is a fixed-size buffer that stores recent tokens or words. It helps maintain a limited but relevant context for language generation or understanding tasks.
class ContextWindow:
def __init__(self, size):
self.size = size
self.window = []
def update(self, token):
if len(self.window) >= self.size:
self.window.pop(0)
self.window.append(token)
def get_context(self):
return ' '.join(self.window)
context = ContextWindow(5)
for word in "The quick brown fox jumps over the lazy dog".split():
context.update(word)
print(f"Current context: {context.get_context()}")
Implementing Attention Mechanism
Attention mechanisms allow models to focus on different parts of the input when generating output, effectively evolving the context based on relevance.
import torch
import torch.nn as nn
class SimpleAttention(nn.Module):
def __init__(self, hidden_size):
super(SimpleAttention, self).__init__()
self.hidden_size = hidden_size
self.attention = nn.Linear(hidden_size * 2, hidden_size)
self.v = nn.Parameter(torch.rand(hidden_size))
def forward(self, hidden, encoder_outputs):
seq_len = encoder_outputs.size(0)
h = hidden.repeat(seq_len, 1, 1).transpose(0, 1)
energy = torch.tanh(self.attention(torch.cat((h, encoder_outputs), 2)))
attention = torch.sum(self.v * energy, dim=2)
return torch.softmax(attention, dim=1)
hidden_size = 256
attention = SimpleAttention(hidden_size)
hidden = torch.rand(1, 1, hidden_size)
encoder_outputs = torch.rand(10, 1, hidden_size)
attn_weights = attention(hidden, encoder_outputs)
print(attn_weights.shape)
Implementing Token-level Context Evolution
Token-level context evolution involves updating the context representation after processing each token in the input sequence.
class TokenLevelContextEvolution:
def __init__(self, vocab_size, embedding_dim):
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, embedding_dim, batch_first=True)
def forward(self, input_sequence):
embedded = self.embeddings(input_sequence)
output, (hidden, cell) = self.lstm(embedded)
return output, hidden
vocab_size = 1000
embedding_dim = 128
model = TokenLevelContextEvolution(vocab_size, embedding_dim)
input_sequence = torch.randint(0, vocab_size, (1, 10))
output, hidden = model(input_sequence)
print(f"Output shape: {output.shape}, Hidden shape: {hidden.shape}")
Implementing Sentence-level Context Evolution
Sentence-level context evolution involves updating the context representation after processing each sentence in the input.
import nltk
nltk.download('punkt')
class SentenceLevelContextEvolution:
def __init__(self, vocab_size, embedding_dim):
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, embedding_dim, batch_first=True)
def forward(self, text):
sentences = nltk.sent_tokenize(text)
sentence_embeddings = []
for sentence in sentences:
tokens = nltk.word_tokenize(sentence)
token_ids = torch.tensor([vocab.get(token, 0) for token in tokens]).unsqueeze(0)
embedded = self.embeddings(token_ids)
_, (hidden, _) = self.lstm(embedded)
sentence_embeddings.append(hidden.squeeze(0))
return torch.stack(sentence_embeddings)
vocab_size = 1000
embedding_dim = 128
model = SentenceLevelContextEvolution(vocab_size, embedding_dim)
text = "This is a sample text. It contains multiple sentences. The context evolves."
output = model(text)
print(f"Output shape: {output.shape}")
Implementing Dynamic Context Windows
Dynamic context windows adjust their size based on the importance or relevance of the content, allowing for more flexible context evolution.
class DynamicContextWindow:
def __init__(self, max_size):
self.max_size = max_size
self.window = []
self.importance_scores = []
def update(self, token, importance):
if len(self.window) >= self.max_size:
min_importance_index = self.importance_scores.index(min(self.importance_scores))
self.window.pop(min_importance_index)
self.importance_scores.pop(min_importance_index)
self.window.append(token)
self.importance_scores.append(importance)
def get_context(self):
return ' '.join(self.window)
context = DynamicContextWindow(5)
tokens = "The quick brown fox jumps over the lazy dog".split()
importances = [0.5, 0.8, 0.6, 0.9, 0.7, 0.4, 0.3, 0.6, 0.5]
for token, importance in zip(tokens, importances):
context.update(token, importance)
print(f"Current context: {context.get_context()}")
Implementing Context-aware Attention
Context-aware attention mechanisms consider both the current input and the evolving context to determine the most relevant information for processing.
class ContextAwareAttention(nn.Module):
def __init__(self, hidden_size):
super(ContextAwareAttention, self).__init__()
self.hidden_size = hidden_size
self.attention = nn.Linear(hidden_size * 3, hidden_size)
self.v = nn.Parameter(torch.rand(hidden_size))
def forward(self, hidden, encoder_outputs, context):
seq_len = encoder_outputs.size(0)
h = hidden.repeat(seq_len, 1, 1).transpose(0, 1)
c = context.repeat(seq_len, 1, 1).transpose(0, 1)
energy = torch.tanh(self.attention(torch.cat((h, encoder_outputs, c), 2)))
attention = torch.sum(self.v * energy, dim=2)
return torch.softmax(attention, dim=1)
hidden_size = 256
attention = ContextAwareAttention(hidden_size)
hidden = torch.rand(1, 1, hidden_size)
encoder_outputs = torch.rand(10, 1, hidden_size)
context = torch.rand(1, 1, hidden_size)
attn_weights = attention(hidden, encoder_outputs, context)
print(attn_weights.shape)
Implementing Hierarchical Context Evolution
Hierarchical context evolution involves maintaining and updating context at multiple levels of granularity, such as word, sentence, and document levels.
class HierarchicalContextEvolution(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_size):
super(HierarchicalContextEvolution, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.word_lstm = nn.LSTM(embedding_dim, hidden_size, batch_first=True)
self.sentence_lstm = nn.LSTM(hidden_size, hidden_size, batch_first=True)
self.document_lstm = nn.LSTM(hidden_size, hidden_size, batch_first=True)
def forward(self, document):
word_contexts = []
for sentence in document:
embedded = self.embeddings(sentence)
_, (word_context, _) = self.word_lstm(embedded)
word_contexts.append(word_context.squeeze(0))
word_contexts = torch.stack(word_contexts)
_, (sentence_context, _) = self.sentence_lstm(word_contexts)
_, (document_context, _) = self.document_lstm(sentence_context.unsqueeze(0))
return document_context.squeeze(0)
vocab_size = 1000
embedding_dim = 128
hidden_size = 256
model = HierarchicalContextEvolution(vocab_size, embedding_dim, hidden_size)
document = [torch.randint(0, vocab_size, (5,)) for _ in range(3)] # 3 sentences, 5 words each
output = model(document)
print(f"Output shape: {output.shape}")
Implementing Context-aware Language Model
A context-aware language model uses the evolving context to generate more coherent and contextually appropriate text.
class ContextAwareLanguageModel(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_size):
super(ContextAwareLanguageModel, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, vocab_size)
def forward(self, input_sequence, context):
embedded = self.embeddings(input_sequence)
lstm_input = torch.cat((embedded, context.unsqueeze(1).repeat(1, embedded.size(1), 1)), dim=2)
output, _ = self.lstm(lstm_input)
logits = self.fc(output)
return logits
vocab_size = 1000
embedding_dim = 128
hidden_size = 256
model = ContextAwareLanguageModel(vocab_size, embedding_dim, hidden_size)
input_sequence = torch.randint(0, vocab_size, (1, 10))
context = torch.rand(1, hidden_size)
output = model(input_sequence, context)
print(f"Output shape: {output.shape}")
Implementing Context-based Text Generation
Context-based text generation uses the evolving context to produce more coherent and contextually relevant text.
def generate_text(model, context, max_length=50, temperature=1.0):
generated = []
current_input = torch.tensor([[0]]) # Start token
for _ in range(max_length):
output = model(current_input, context)
output = output[:, -1, :] / temperature
probabilities = torch.softmax(output, dim=-1)
next_token = torch.multinomial(probabilities, 1)
generated.append(next_token.item())
current_input = torch.cat((current_input, next_token), dim=1)
if next_token.item() == 1: # End token
break
return generated
# Assume we have a trained ContextAwareLanguageModel called 'trained_model'
context = torch.rand(1, hidden_size)
generated_tokens = generate_text(trained_model, context)
print("Generated tokens:", generated_tokens)
Evaluating Context Evolution
Evaluating the effectiveness of context evolution involves measuring the model's performance on various downstream tasks and analyzing the quality of generated text.
def evaluate_perplexity(model, data_loader, context):
model.eval()
total_loss = 0
total_tokens = 0
with torch.no_grad():
for batch in data_loader:
input_ids, labels = batch
logits = model(input_ids, context)
loss = nn.CrossEntropyLoss()(logits.view(-1, logits.size(-1)), labels.view(-1))
total_loss += loss.item() * labels.numel()
total_tokens += labels.numel()
perplexity = torch.exp(torch.tensor(total_loss / total_tokens))
return perplexity.item()
# Assume we have a DataLoader called 'test_loader' and a trained model 'trained_model'
context = torch.rand(1, hidden_size)
perplexity = evaluate_perplexity(trained_model, test_loader, context)
print(f"Perplexity: {perplexity:.2f}")
Fine-tuning for Context Evolution
Fine-tuning a pre-trained language model for better context evolution involves adapting the model to specific tasks or domains while maintaining its contextual understanding.
from transformers import GPT2LMHeadModel, GPT2Tokenizer, AdamW
def fine_tune_gpt2(model, tokenizer, train_data, epochs=3, learning_rate=5e-5):
model.train()
optimizer = AdamW(model.parameters(), lr=learning_rate)
for epoch in range(epochs):
total_loss = 0
for batch in train_data:
inputs = tokenizer(batch, return_tensors="pt", padding=True, truncation=True)
outputs = model(**inputs, labels=inputs["input_ids"])
loss = outputs.loss
total_loss += loss.item()
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss/len(train_data):.4f}")
return model
# Example usage
model = GPT2LMHeadModel.from_pretrained("gpt2")
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
train_data = ["Example sentence 1", "Example sentence 2", "Example sentence 3"]
fine_tuned_model = fine_tune_gpt2(model, tokenizer, train_data)
Additional Resources
For further exploration of context evolution in language models, consider the following research papers:
- "Attention Is All You Need" by Vaswani et al. (2017) arXiv: https://arxiv.org/abs/1706.03762
- "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding" by Devlin et al. (2018) arXiv: https://arxiv.org/abs/1810.04805
- "Language Models are Few-Shot Learners" by Brown et al. (2020) arXiv: https://arxiv.org/abs/2005.14165
- "Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer" by Raffel et al. (