In-Context Learning: как LLM учится без обновления весов
Введение: что такое In-Context Learning?
Обычное обучение (Fine-Tuning):
1. Берём модель
2. Обновляем веса на данных
3. Модель запоминает навсегда
In-Context Learning (ICL):
1. Берём модель (веса НЕ меняем!)
2. Показываем примеры в промпте
3. Модель "учится" на время сессии
4. После окончания сессии — забывает
In-Context Learning — это способность LLM обучаться на примерах, предоставленных в контексте промпта, без обновления весов модели.
Это одно из самых удивительных свойств больших языковых моделей. Вы просто показываете несколько примеров решения задачи, и модель начинает её решать — без какого-либо обучения, без обновления весов, без градиентов.
Почему ICL работает?
LLM — это не просто "предсказатель следующего токена".
Она обладает способностью:
1. Понимать паттерны из примеров
2. Применять их к новым задачам
3. Адаптироваться к формату
Это возможно благодаря:
- Огромному количеству данных при pretraining
- Attention механизму, который "фокусируется" на примерах
- Трансформерной архитектуре, которая может имитировать обучение
Научное объяснение
ICL связан с мета-обучением (meta-learning).
LLM обучается при pretraining НЕ ТОЛЬКО:
"предсказывать следующий токен"
НО И:
"учиться предсказывать следующий токен
на основе примеров в контексте"
Это как если бы вы учились НЕ ТОЛЬКО решать задачи,
НО И учиться решать новые типы задач по примерам.
Исследование Wei et al., 2022, представившее концепцию ICL, показало что способность к in-context learning возникает при определённом размере модели — примерно от 100B параметров. Меньшие модели практически не способны к ICL.
Zero-Shot vs Few-Shot vs One-Shot
Zero-Shot ICL
Prompt:
"Переведите на французский: 'Hello'"
Ответ:
"Bonjour"
Без примеров! Модель использует знания из pretraining.
Zero-shot — самый простой вариант. Вы просто просите модель выполнить задачу без каких-либо примеров. Модель полагается на знания, полученные при pretraining.
One-Shot ICL
Prompt:
Переведите на французский:
Hello → Bonjour
Goodbye → ?
Ответ:
"Au revoir"
Один пример показывает паттерн.
One-shot — один пример помогает модели понять формат ответа и специфику задачи.
Few-Shot ICL
Prompt:
Переведите на французский:
Hello → Bonjour
Goodbye → Au revoir
Thank you → Merci
Please → S'il vous plaît
Sorry → ?
Ответ:
"Désolé"
Больше примеров = лучше понимание паттерна.
Few-shot — 3-5 примеров обычно дают наилучший результат. Слишком много примеров может "сбить с толку" модель или превысить лимит контекста.
Сравнение
| Zero-Shot | One-Shot | Few-Shot (3-5)
------------------|---------------|---------------|------------------
Точность | 60-80% | 70-85% | 80-95%
Длина промпта | Короткий | Средний | Длиннее
Стоимость | Дешевле | Средне | Дороже
Надёжность | Низкая | Средняя | Высокая
Скорость | Быстрее | Средне | Медленнее
Форматирование промптов для ICL
Паттерн 1: Вопрос-Ответ
Q: Какова столица Франции?
A: Париж
Q: Какова столица Японии?
A: Токио
Q: Какова столица Бразилии?
A:
Простой и эффективный паттерн для фактических вопросов. Модель понимает формат и продолжает его.
Паттерн 2: Текст-Метка (Классификация)
Текст: "Отличный продукт! Рекомендую всем."
Метка: позитивный
Текст: "Ужасное обслуживание, никогда больше!"
Метка: негативный
Текст: "Доставка была быстрой, но товар повреждён."
Метка:
Для классификации текстов. Модель понимает что нужно предсказать метку класса.
Паттерн 3: Исходный код-Комментарий
# Исходный код:
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Комментарий: "Рекурсивная реализация чисел Фибоначчи"
# Исходный код:
def quicksort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr)//2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)
# Комментарий:
Для генерации комментариев к коду. Модель понимает паттерн форматирования.
Паттерн 4: Structured ICL (JSON)
{
"input": "Какая погода в Москве?",
"output": {
"city": "Москва",
"temperature": 22,
"condition": "ясно"
}
}
{
"input": "Какая погода в Париже?",
"output": {
"city": "Париж",
"temperature": 18,
"condition": "облачно"
}
}
{
"input": "Какая погода в Токио?",
"output":
Для структурированных ответов. JSON формат помогает получить предсказуемый вывод.
Паттерн 5: Chain-of-Thought ICL
Q: У меня есть 5 яблок. Я купил ещё 3 и съел 2. Сколько у меня яблок?
A: Давайте посчитаем пошагово:
1. Было: 5 яблок
2. Купили: +3 яблока
3. Съели: -2 яблока
4. Итого: 5 + 3 - 2 = 6 яблок
Ответ: 6
Q: У меня есть 10 конфет. Я отдал 4 другу и купил ещё 7. Сколько у меня конфет?
A:
Chain-of-Thought ICL показывает модели как рассуждать пошагово. Это значительно улучшает качество ответов на математические и логические задачи.
Порядок примеров имеет значение
Исследование показывает:
1. Порядок примеров влияет на результат
2. Первые примеры (primacy effect) важнее
3. Последние примеры (recency effect) тоже важны
4. Случайная перестановка может изменить ответ
Рекомендации:
- Сортировать примеры по релевантности
- Использовать разнообразные примеры
- Избегать однообразных паттернов
Эксперимент с порядком
import random
def test_order_sensitivity(model, examples, question):
"""Тестируем чувствительность к порядку примеров."""
answers = []
for i in range(10):
# Перемешиваем примеры
shuffled = random.sample(examples, len(examples))
prompt = build_prompt(shuffled, question)
answer = model.generate(prompt)
answers.append(answer)
# Проверяем согласованность
unique_answers = set(answers)
print(f"Уникальных ответов: {len(unique_answers)}")
print(f"Согласованность: {1 - len(unique_answers)/10:.1%}")
# Если < 80% согласованности — модель нестабильна
Оптимальная стратегия сортировки
1. Сначала самые релевантные примеры
2. Затем разнообразные примеры
3. В конце — примеры с похожим форматом на целевой
Пример для классификации:
1. Пример из ТОГО ЖЕ домена (релевантность)
2. Пример из другого домена (разнообразие)
3. Пример с неоднозначным текстом (сложность)
Выбор примеров для ICL
Критерии выбора
Хорошие примеры для ICL:
1. Релевантны задаче (тот же домен)
2. Разнообразны (разные варианты)
3. Правильные (без ошибок)
4. Чёткие (однозначные ответы)
5. В правильном формате
Плохие примеры:
1. Из другой предметной области
2. Противоречивые
3. С ошибками
4. Двусмысленные
5. В неправильном формате
Retrieval-Augmented ICL
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
class RetrievalAugmentedICL:
"""
ICL с ретривингом примеров из базы знаний.
Вместо случайных примеров — выбираем наиболее
релевантные текущему запросу.
"""
def __init__(self, example_db):
self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
self.examples = example_db
self.index = self._build_index()
def _build_index(self):
"""Строим FAISS индекс для эмбеддингов примеров."""
embeddings = self.encoder.encode(
[ex['input'] for ex in self.examples]
)
dimension = embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(embeddings.astype('float32'))
return index
def retrieve(self, query, k=4):
"""Извлекаем k наиболее релевантных примеров."""
query_embedding = self.encoder.encode([query])
distances, indices = self.index.search(
query_embedding.astype('float32'), k
)
selected_examples = [
self.examples[idx] for idx in indices[0]
]
return selected_examples
def build_prompt(self, query, examples):
"""Строим промпт с отретривленными примерами."""
prompt = ""
for ex in examples:
prompt += f"Input: {ex['input']}\nOutput: {ex['output']}\n\n"
prompt += f"Input: {query}\nOutput:"
return prompt
# Использование
icl = RetrievalAugmentedICL(example_db=training_data)
examples = icl.retrieve("Какая погода в Москве?")
prompt = icl.build_prompt("Какая погода в Москве?", examples)
response = model.generate(prompt)
Adaptive ICL
class AdaptiveICL:
"""
ICL с адаптивным выбором количества примеров.
Для простых запросов — меньше примеров (дешевле).
Для сложных — больше примеров (точнее).
"""
def __init__(self, model, example_db):
self.model = model
self.icl = RetrievalAugmentedICL(example_db)
self.simple_threshold = 0.7
def estimate_complexity(self, query):
"""Оцениваем сложность запроса."""
# Простой эвристический подход
words = query.split()
has_question = '?' in query
length_penalty = min(len(words) / 20, 1.0)
complexity = 1.0 - (0.3 if has_question else 0.0) + length_penalty
return min(complexity, 1.0)
def select_num_examples(self, query):
"""Выбираем количество примеров на основе сложности."""
complexity = self.estimate_complexity(query)
if complexity < self.simple_threshold:
return 1 # Zero/one-shot для простых
elif complexity < 0.8:
return 3 # Few-shot для средних
else:
return 5 # More examples для сложных
def generate(self, query):
"""Генерируем ответ с адаптивным ICL."""
num_examples = self.select_num_examples(query)
examples = self.icl.retrieve(query, k=num_examples)
prompt = self.icl.build_prompt(query, examples)
return self.model.generate(prompt)
ICL для кодирования
Генерация кода
Напиши функцию на Python:
Пример 1:
Задача: найти максимум в списке
Код: def find_max(lst):
return max(lst)
Пример 2:
Задача: проверить палиндром
Код: def is_palindrome(s):
return s == s[::-1]
Пример 3:
Задача: посчитать частоту символов
Код:
Code Completion с ICL
# Паттерн проекта: используем dependency injection
class UserService:
def __init__(self, db: Database):
self.db = db
def get_user(self, user_id: int) -> User:
return self.db.query(User).filter_by(id=user_id).first()
class AuthService:
def __init__(self, db: Database, user_service: UserService):
self.db = db
self.user_service = user_service
def authenticate(self, username: str, password: str) -> bool:
user = self.user_service.get_user(username)
return user and user.check_password(password)
# Паттерн проекта: используем event sourcing
Code Review с ICL
Код:
def calculate_total(items):
total = 0
for item in items:
total = total + item['price'] * item['quantity']
return total
Ревью:
- ✅ Логика правильная
- ⚠️ Можно оптимизировать через sum() + generator
- 💡 Добавить типизацию аргументов
- 💡 Обработать пустой список
Улучшенная версия:
def calculate_total(items: list[dict]) -> float:
return sum(
item['price'] * item['quantity']
for item in items
)
Ограничения ICL
Когда ICL НЕ работает
1. Слишком много примеров (>10-15)
→ Модель "теряет" первые примеры
2. Противоречивые примеры
→ Модель путается в паттернах
3. Очень специфичные домены
→ Модель не знает контекста (медицина, право)
4. Требуются точные числа/факты
→ Модель может "галлюцинировать"
5. Очень длинные зависимости
→ Attention не справляется с дальними связями
Эффект длины контекста
Исследование показывает что качество ICL зависит от
позиции примеров в контексте:
Позиция примера | Качество ICL
-------------------|---------------
1-2 (начало) | 90-95% ⭐
3-5 (середина) | 80-90% ★
6-8 (конец) | 75-85% ★
9+ (очень далеко) | 50-70% ⚠️
Вывод: размещайте самые важные примеры в начале.
Галлюцинации в ICL
Проблема: модель может "додумать" пример, которого нет.
Пример:
Примеры:
"Apple → компания"
"Google → компания"
"Amazon → компания"
Запрос: "Москва → ?"
Ответ: "компания" ❌ (галлюцинация!)
Правильный ответ: "город"
Модель "увидела" паттерн где его нет.
Практические рекомендации
Для начинающих
1. Начните с zero-shot
→ Быстро, дёшево, просто
2. Если результат плохой — добавьте 3-5 примеров
→ Few-shot обычно решает проблему
3. Примеры должны быть:
→ Из того же домена
→ В правильном формате
→ Без ошибок
4. Экспериментируйте с порядком примеров
→ Попробуйте разные варианты
→ Выберите лучший
Для продвинутых
1. Используйте retrieval-augmented ICL
→ Автоматический выбор лучших примеров
2. Адаптируйте количество примеров
→ Простые задачи — 1-2 примера
→ Сложные — 5-8 примеров
3. Комбинируйте с chain-of-thought
→ Показывайте рассуждения в примерах
4. Используйте structured output
→ JSON, YAML, другие форматы
5. Анализируйте ошибки
→ Какие примеры приводят к ошибкам?
→ Удалите или замените их
Шаблон для production ICL
class ProductionICL:
"""
Production-ready ICL система.
Включает:
- Кэширование примеров
- A/B тестирование форматов
- Мониторинг качества
- Автоматическое обновление примеров
"""
def __init__(self):
self.icl = RetrievalAugmentedICL(self.example_db)
self.cache = {}
self.metrics = QualityTracker()
def generate_with_quality_check(self, query, max_retries=3):
"""Генерируем ответ с проверкой качества."""
for attempt in range(max_retries):
examples = self.icl.retrieve(query)
prompt = self.icl.build_prompt(query, examples)
response = self.model.generate(prompt)
# Проверяем качество
quality_score = self.metrics.evaluate(response)
if quality_score > self.quality_threshold:
self.metrics.record(query, response, quality_score)
return response
# Если качество низкое — пробуем с другими примерами
examples = self.icl.retrieve(query, k=5)
return self.fallback_response(query)
def update_examples(self, new_examples, validation_score):
"""Обновляем базу примеров на основе качества."""
if validation_score > self.best_score:
self.example_db.add(new_examples)
self.best_score = validation_score
self._rebuild_index()
ICL vs Fine-Tuning vs Pre-training
| ICL | Fine-Tuning | Pre-training
------------------|---------------|---------------|---------------
Стоимость | Очень низкая | Средняя | Очень высокая
Время | Мгновенно | Часы/дни | Недели/месяцы
Нужны данные | Нет | Да (100-10K) | Да (миллиарды)
Гибкость | Высокая | Средняя | Низкая
Знания | Ограничены | Умеренные | Полные
Обновляемость | Легко | Сложно | Невозможно
Лучшее для | Быстрые задачи | Специфичные | Базовые знания
Когда что использовать
ICL:
✅ Быстрый прототип
✅ Задачи с несколькими примерами
✅ Частые изменения формата
✅ Нет данных для обучения
Fine-Tuning:
✅ Специфичный домен (медицина, право)
✅ Много данных для обучения
✅ Нужна стабильность поведения
✅ Частое использование
Pre-training:
✅ Новый язык или домен
✅ Огромные ресурсы
✅ Создание новой модели с нуля
Заключение
In-Context Learning — это мощная способность LLM, которая позволяет решать задачи без обновления весов. Ключевые выводы:
- ICL работает благодаря pretraining — модель учится "учиться на примерах"
- Формат промпта критически важен — правильные примеры = правильный ответ
- Few-shot (3-5 примеров) обычно оптимален — больше не всегда лучше
- Порядок примеров влияет на результат — экспериментируйте
- Retrieval-augmented ICL — автоматический выбор лучших примеров для production
ICL — это ваш первый инструмент при работе с LLM. Пробуйте zero-shot, затем few-shot, и только если не помогает — рассмотрите fine-tuning.