Что такое fine-tuning языковых моделей?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Fine-tuning языковых моделей
Fine-tuning — это процесс адаптации предварительно обученной языковой модели к конкретной задаче или домену путём доподнительного обучения на специализированных данных. Вместо обучения модели с нуля, мы берём уже обученную модель и приспосабливаем её веса к новой задаче.
Общая архитектура процесса
Все современные методы fine-tuning следуют одной схеме:
1. Загружаем предобученную модель (BERT, GPT, T5, LLaMA и т.д.)
2. Инициализируем веса из предобучения
3. Добавляем task-specific слои (если нужно)
4. Обучаем на целевом датасете
5. Оцениваем на тестовом наборе
Почему fine-tuning работает лучше, чем обучение с нуля?
1. Transfer Learning Предобученная модель уже изучила основные признаки языка: синтаксис, семантику, факты. Нам остаётся адаптировать эти знания к конкретной задаче.
2. Меньше данных Для fine-tuning нужно меньше данных, чем для полного обучения, так как модель уже знает язык.
3. Быстрее обучение Можно использовать более низкий learning rate и меньше эпох, так как веса уже хорошо инициализированы.
4. Лучше производительность Модель получает лучший результат на малых датасетах благодаря знаниям из предобучения.
Простой пример fine-tuning с BERT
import torch
from torch.utils.data import DataLoader, Dataset
from transformers import BertTokenizer, BertForSequenceClassification
from torch.optim import AdamW
# 1. Загружаем предобученную модель
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForSequenceClassification.from_pretrained(
'bert-base-uncased',
num_labels=2 # Бинарная классификация
)
# 2. Подготовка данных
class TextDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len=128):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = self.texts[idx]
label = self.labels[idx]
encoding = self.tokenizer(
text,
add_special_tokens=True,
max_length=self.max_len,
padding='max_length',
truncation=True,
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].squeeze(),
'attention_mask': encoding['attention_mask'].squeeze(),
'label': torch.tensor(label)
}
# 3. Подготовка data loader
train_texts = ["This movie is great!", "I hated this film."]
train_labels = [1, 0] # 1 = положительный, 0 = отрицательный
train_dataset = TextDataset(train_texts, train_labels, tokenizer)
train_loader = DataLoader(train_dataset, batch_size=8)
# 4. Настройка обучения
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
optimizer = AdamW(model.parameters(), lr=2e-5)
epochs = 3
# 5. Обучение
for epoch in range(epochs):
model.train()
total_loss = 0
for batch in train_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
optimizer.zero_grad()
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch + 1}, Loss: {total_loss / len(train_loader):.4f}")
# 6. Инференс
model.eval()
with torch.no_grad():
test_text = "This movie is wonderful!"
encoding = tokenizer(test_text, return_tensors='pt')
encoding = {k: v.to(device) for k, v in encoding.items()}
outputs = model(**encoding)
logits = outputs.logits
prediction = torch.argmax(logits, dim=1).item()
print(f"Prediction: {prediction}") # 1 = положительный
Различные стратегии fine-tuning
1. Полный fine-tuning (Full Fine-tuning)
Обновляются все параметры модели:
# Все параметры требуют градиентов
for param in model.parameters():
param.requires_grad = True
# Обучаем со слегка пониженным learning rate
optimizer = AdamW(model.parameters(), lr=2e-5)
Преимущества: Лучшая производительность, полная адаптация модели Недостатки: Много параметров, высокий риск переобучения
2. Partial Fine-tuning (Замораживание слоёв)
Обновляют только верхние слои, нижние слои заморожены:
# Замораживаем нижние слои BERT
for name, param in model.bert.encoder.layer[:10].named_parameters():
param.requires_grad = False
# Обновляем только верхние слои и классификационный слой
optimizer = AdamW(
[p for p in model.parameters() if p.requires_grad],
lr=1e-4
)
Преимущества: Меньше параметров, быстрее обучение, меньше риск переобучения Недостатки: Может быть менее точным
3. LoRA (Low-Rank Adaptation)
Добавляют обучаемые матрицы низкого ранга к весам, вместо обновления всех весов:
from peft import get_peft_model, LoraConfig, TaskType
# Конфигурируем LoRA
lora_config = LoraConfig(
r=8, # Ранг матриц
lora_alpha=16,
target_modules=['q_proj', 'v_proj'], # Какие слои адаптировать
lora_dropout=0.05,
bias='none',
task_type=TaskType.SEQ_2_SEQ_LM
)
# Применяем LoRA к модели
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# Output: trainable params: 2,097,152 || all params: 350,127,616 || trainable%: 0.5986
Преимущества: Очень мало параметров (~1% от исходных), быстро и дёшево Недостатки: Иногда менее точно чем полный fine-tuning
4. Prompt Tuning
Обучают только специальные prefix-токены (prompt), остальная модель заморожена:
class PromptTuning(torch.nn.Module):
def __init__(self, model, prompt_len=10, hidden_dim=768):
super().__init__()
self.model = model
self.prompt_embeddings = torch.nn.Embedding(prompt_len, hidden_dim)
# Замораживаем исходную модель
for param in model.parameters():
param.requires_grad = False
def forward(self, input_ids, attention_mask):
batch_size = input_ids.shape[0]
# Получаем эмбеддинги prompt
prompt_embs = self.prompt_embeddings(
torch.arange(self.prompt_embeddings.num_embeddings)
).unsqueeze(0).expand(batch_size, -1, -1)
# Получаем эмбеддинги входа
input_embs = self.model.embeddings(input_ids)
# Конкатенируем prompt и input
combined_embs = torch.cat([prompt_embs, input_embs], dim=1)
return self.model.encoder(combined_embs)
Преимущества: Экстремально мало параметров, мультитасковое обучение Недостатки: Менее универсально, может быть нестабильным
Гиперпараметры fine-tuning
# Типичные гиперпараметры для fine-tuning
learning_rate = 2e-5 # Ниже чем обучение с нуля (обычно 1e-4 или 1e-3)
epochs = 3 # Обычно 2-5 эпох
warmup_steps = 500 # Постепенное увеличение lr
weight_decay = 0.01 # L2 регуляризация
batch_size = 8 # Может быть меньше
max_grad_norm = 1.0 # Обрезание градиентов
Лучшие практики fine-tuning
1. Выбор learning rate
- Начните с 2e-5 для BERT, меньше для больших моделей
- Используйте learning rate scheduler (linear decay, cosine annealing)
2. Обработка дисбаланса классов
from sklearn.utils.class_weight import compute_class_weight
class_weights = compute_class_weight(
'balanced',
classes=np.unique(labels),
y=labels
)
class_weights = torch.FloatTensor(class_weights)
3. Регуляризация
- Dropout, weight decay
- Ранняя остановка (early stopping)
4. Валидация
# Валидируем каждую эпоху
for epoch in range(epochs):
train_loss = train_epoch(model, train_loader)
val_loss, val_acc = validate(model, val_loader)
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), 'best_model.pt')
patience_counter = 0
else:
patience_counter += 1
if patience_counter >= 3:
break
Размеры датасетов для fine-tuning
- Маленькие датасеты (100-1000): LoRA или Prompt Tuning
- Средние датасеты (1000-10000): Partial Fine-tuning или Full Fine-tuning с регуляризацией
- Большие датасеты (100000+): Full Fine-tuning или даже предобучение нового токенайзера
Заключение
Fine-tuning — это один из самых эффективных способов адаптации языковых моделей к новым задачам. Выбор стратегии зависит от размера датасета, доступных вычислительных ресурсов и требуемой точности. В 2024-2025 годах LoRA стал стандартом для эффективного fine-tuning больших моделей.