Продвинутые методы NLP

На самом деле мы взаимодействуем с NLP каждый день — подсказки слов при наборе текста на клавиатуре телефона, спам-письма, автоматически отфильтровываемые вашим почтовым приложением, и иностранные сайты, понимать которые помогает программное обеспечение для перевода. Всё это — применения обработки естественного языка (NLP).

До появления ChatGPT NLP уже развивался десятилетиями, но технический порог был высок. Нужно было понимать длинный список концепций, таких как токенизация, частеречная разметка, синтаксический анализ и разметка семантических ролей, а также вручную проектировать признаки, чтобы машины могли "понимать" хотя бы немного языка.

Сегодня большие языковые модели делают всё это простым. Но понимание технической эволюции NLP позволяет вам более глубоко понять, почему большие модели могут делать эти вещи и в чём заключаются их ограничения.

Этот модуль проведёт вас от векторов слов до современных больших языковых моделей, создавая полную систему знаний по NLP.。

Путь обучения: векторы слов → предобученные модели → три основные парадигмы BERT/GPT/T5 → нисходящие задачи (классификация, NER, перевод, суммаризация). Каждый шаг сопровождается соответствующими примерами кода, которые помогают применить теорию на практике.


Эволюция предобученных языковых моделей

Развитие NLP можно чётко разделить на несколько этапов, каждый из которых сопровождался знаковыми технологическими прорывами.

Word2Vec: Революция в векторных представлениях слов

До 2013 года способ, которым компьютеры обрабатывали текст, был очень примитивным.

Обычно использовалось "One-Hot Encoding": каждому слову соответствует чрезвычайно длинный вектор, в котором только одна позиция равна 1, а остальные — 0. Например, если словарь содержит 100,000 слов, каждое слово — это 100,000-мерный вектор.

Проблема этого подхода очевидна: векторы не содержат семантической информации. Расстояние между "кошкой" и "собакой" такое же, как расстояние между "кошкой" и "столом".

Основная идея Word2Vec:Значение слова определяется словами вокруг него.。

Пример

# ============================================
# Демонстрация базовых понятий Word2Vec
# Использование библиотеки gensim для обучения простой модели векторного представления слов
# ============================================

# Сначала установите gensim: pip install gensim

from gensim.models import Word2Vec
import numpy as np

# Подготовка обучающих данных: несколько простых предложений
sentences = [
    ["I", "like", "eat", "apple"],
    ["I", "like", "eat", "banana"],
    ["кошка", "любит", "есть", "рыбу"],
    ["собака", "любит", "есть", "мясо"],
    ["яблоко", "это", "разновидность", "фрукта"],
    ["банан", "это", "разновидность", "фрукта"],
    ["кошка", "это", "разновидность", "животного"],
    ["собака", "это", "разновидность", "животного"],
    ["EXAMPLE", "это", "a", программирование, веб-сайт],
    [Изучение, программирования, Go, "EXAMPLE"],
]

# Обучение модели Word2Vec
# vector_size: размерность векторов слов
# window: размер контекстного окна (просматривать несколько слов до и после)
# min_count: игнорировать слова с частотой встречаемости меньше этого значения
# workers: количество потоков для параллельного обучения
model = Word2Vec(
    sentences=sentences,
    vector_size=50,  # каждое слово представляется 50-мерным вектором
    window=3,       # учитывать 3 слова до и после
    min_count=1,    # сохранять все слова
    workers=4,
    epochs=100      # обучение в течение 100 эпох
)

# получить вектор слова
apple_vector = model.wv[apple]
print(f'apple' вектор слова (первые 10 измерений): {apple_vector[:10]})
print(fРазмерность вектора слова: {len(apple_vector)})

# вычислить сходство между словами
similarity = model.wv.similarity(apple, banana)
print(fСходство 'apple' и 'banana': {similarity:.4f})

similarity = model.wv.similarity(apple, cat)
print(fСходство 'apple' и 'cat': {similarity:.4f})

# найти наиболее похожие слова
print("\nСлова, наиболее похожие на 'cat':)
for word, score in model.wv.most_similar(cat, topn=3):
    print(f" {word}: {score:.4f}")

print("\nСлова, наиболее похожие на 'EXAMPLE':)
for word, score in model.wv.most_similar("EXAMPLE", topn=3):
    print(f" {word}: {score:.4f}")

# классическая арифметика словесных векторов: король - мужчина + женщина ≈ королева
# попробуйте в нашем маленьком корпусе: фрукт - яблоко + рыба ≈ ?
if apple in model.wv and fish in model.wv and fruit in model.wv:
    result = model.wv.most_similar(positive=[fruit, fish], negative=[apple], topn=3)
    print("\n'fruit' - 'apple' + 'fish' ≈)
    for word, score in result:
        print(f" {word}: {score:.4f}")

Успех Word2Vec доказывает одну вещь:Семантику можно представить с помощью векторных пространств.。

Но у него есть ограничение: каждое слово имеет только один фиксированный вектор, независимо от контекста. Например, "打" в "打电speech" (позвонить по телефону) и "打游戏" (играть в игры) имеет разные значения, но Word2Vec даёт один и тот же вектор.

ELMo: Контекстуальные векторные представления слов

ELMo (Embeddings from Language Models), представленный в 2018 году, решил эту проблему.

Идея ELMo такова:Он не назначает заранее фиксированный вектор каждому слову; вместо этого он смотрит на всё предложение и затем генерирует вектор для этого слова.。

Один и тот же иероглиф “打” является одним вектором в “I打电speech” и другим вектором в “I打游戏”.

ELMo использует двунаправленную LSTM (Long Short-Term Memory networks) для моделирования контекста, что стало первым крупномасштабным применением парадигмы «предобучение + дообучение».

GPT-1: однонаправленное предобучение

Также в 2018 году OpenAI выпустила GPT-1 (Generative Pre-training Transformer).

Его особенности:

1. Использовать декодер Transformer вместо LSTM.

2. Однонаправленность: учитывать только предыдущие слова и предсказывать следующее слово.

3. Генеративность: может продолжать писать текст.

GPT-1 продемонстрировал большой потенциал Transformer в задачах NLP.

BERT: двунаправленное предобучение

В конце 2018 года компания Google выпустила BERT (Bidirectional Encoder Representations from Transformers), что полностью преобразило область NLP.

Ключевая инновация BERT заключается в следующем:

Двунаправленность: одновременно учитывать как предшествующий, так и последующий контекст.

2. MLM (Masked Language Model): случайным образом маскировать некоторые слова и позволять модели предсказывать их.

3. NSP (Next Sentence Prediction): определять, являются ли два предложения последовательными.

BERT показала лучшие на тот момент результаты в 11 задачах NLP, что ознаменовало вступление NLP в "эру предобученных моделей".

Скачок от GPT-3 к ChatGPT

В 2020 году была выпущена GPT-3, количество параметров которой достигло 175 миллиардов.

Люди обнаружили, что когда модель достаточно большая, а данных достаточно много, возникает явление "эмерджентности" — модель внезапно приобретает способности, которых нет у маленьких моделей, такие как обучение по нескольким примерам и сложные рассуждения.

В конце 2022 года был выпущен ChatGPT, использующий RLHF (обучение с подкреплением на основе обратной связи от людей), чтобы выходные данные модели лучше соответствовали человеческим предпочтениям, и ИИ по-настоящему вошёл в мейнстрим.

Давайте обобщим эту историю эволюции с помощью таблицы:

годmodelключевая идеяИсторический статус
2013Word2VecИспользовать слова, окружающие слово, для определения его значения.Революция в векторных представлениях слов: векторизованное представление семантики.
2018ELMoКонтекстно-зависимые векторы словПервая реализация векторного представления многозначности.
2018GPT-1Декодер Transformer, однонаправленное предобучение.Продемонстрировать потенциал трансформеров.
2018BERTКодировщик Transformer, двунаправленное предобучениеNLP вступает в эру предобученных моделей
2020GPT-3175 миллиардов параметров, эмерджентные способностиДемонстрация бесконечных возможностей больших моделей
2022ChatGPTRLHF + способности к диалогуИИ по-настоящему доходит до широких масс

Глубокое погружение в BERT

BERT — веха в истории NLP, и он заслуживает нашего глубокого понимания.

Задача MLM (маскированная языковая модель)

Основная задача предобучения BERT — MLM: случайным образом заменить 15% слов в предложении на [MASK] и позволить модели предсказать, каким было исходное слово.

Например, предложение: "Я люблю есть яблоки" может стать: "Я [MASK] есть яблоки", и модель должна предсказать, что [MASK] — это "люблю".

Зачем это делать? Потому что это заставляет модель одновременно использовать информацию из окружающего контекста, а не только слева или справа.

На практике не все эти 15% слов заменяются на [MASK], а именно:

1. В 80% случаев заменить на [MASK]

2. В 10% случаев заменить на случайное слово

3. В 10% случаев оставить исходное слово без изменений

Это делается, чтобы сделать модель более устойчивой — она не может быть уверена, было ли слово заменено, поэтому должна по-настоящему понимать контекст.

Задача NSP (предсказание следующего предложения)

Вторая задача предобучения BERT — это NSP: даны два предложения A и B, нужно определить, является ли B истинным следующим предложением после A.

Положительный пример: A="Я люблю есть яблоки", B="Яблоки — это фрукты"

Отрицательный пример: A="Я люблю есть яблоки", B="Сегодня погода действительно хорошая"

Эта задача помогает модели понимать связи между предложениями, что очень полезно для таких задач, как ответы на вопросы и логический вывод на естественном языке.

Сценарии применения BERT

BERT — это архитектура «энкодера», хорошо понимающая язык, подходит для:

Классификация текста, анализ тональности, распознавание именованных сущностей, ответы на вопросы, логический вывод на естественном языке и т. д.

Он не очень хорошо генерирует текст, а это сильная сторона GPT.

Стандартный процесс тонкой настройки BERT

Давайте на практике используем библиотеку Hugging Face Transformers для тонкой настройки BERT для классификации текста:

Пример

# ============================================
# Тонкая настройка BERT для классификации текста с помощью Hugging Face
# Задача: определить, имеет ли предложение положительную или отрицательную тональность
# ============================================

# Сначала установите зависимости:
# pip install transformers datasets torch scikit-learn

import torch
from transformers import (
    BertTokenizer,
    BertForSequenceClassification,
    TrainingArguments,
    Trainer,
)
from datasets import Dataset, load_metric
import numpy as np
from sklearn.model_selection import train_test_split

# ============================================
# 1. Подготовка данных
# ============================================

# Создание простого набора данных для классификации тональности
texts = [
    "Этот товар действительно хорош в использовании, мне он очень нравится",
    "Качество слишком плохое, очень разочаровывает",
    "Учебник EXAMPLE явно легко усваивается",
    Логистика очень медленная, и упаковка тоже повреждена,
    Производительность камеры этого телефона превосходна,
    Обслуживание и отношение, больше не приду,
    Фильм чудесный, рекомендую к покупке,
    Фильм очень скучный, заснул во время просмотра,
    Еда в этом ресторане очень вкусная,
    В игре слишком много багов, плохое впечатление,
    Учебник EXAMPLE по NLP очень полезен,
    Интерфейс этого программного обеспечения хорошо продуман,
    Курьер очень дружелюбен,
    Размер одежды указан неверно, и цвет тоже неправильный,
    В отеле очень тихо, спалось хорошо,
    Служба поддержки отвечает очень медленно, проблема не решена,
]

labels = [
    1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0
]  # 1=положительный, 0=отрицательный

# Разделить обучающую и валидационную выборки
train_texts, val_texts, train_labels, val_labels = train_test_split(
    texts, labels, test_size=0.25, random_state=42
)

# Создать объект Dataset
train_dataset = Dataset.from_dict({"text": train_texts, "label": train_labels})
val_dataset = Dataset.⟦⟧from_dictfrom_dict({"text": val_texts, "label": val_labels})

# ============================================
# 2. Загрузка токенизатора и модели
# ============================================

# Использование китайской модели BERT
model_name = "bert-base-chinese"

tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(
    model_name,
    num_labels=2  # Задача бинарной классификации
)

# ============================================
# 3. Предобработка данных: преобразование текста во входные данные модели
# ============================================

def tokenize(examples):
    return tokenizer(
        examples["text"]],
        padding="max_length",
        truncation=True,
        max_length=64
    )

# Применить предобработку
tokenized_train = train_dataset.map(tokenize_function, batched=True)
tokenized_val = val_dataset.map(tokenize_function, batched=True)

# ============================================
# 4. Определить метрики оценки
# ============================================

metric = load_metric("accuracy")

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)

# ============================================
# 5. Настроить параметры обучения
# ============================================

training_args = TrainingArguments(
    output_dir="./example-bert-sentiment",  # Выходной каталог
    num_train_epochs=3,                    # Количество эпох обучения
    per_device_train_batch_size=4,         # Размер пакета для обучения
    per_device_eval_batch_size=4,          # Размер батча для оценки
    warmup_steps=5,                        # Шаги разогрева
    weight_decay=0.01,                     # Затухание весов
    logging_dir="./logs",                  # Каталог логов
    logging_steps=10,
    evaluation_strategy="epoch",           # Оценивать один раз за эпоху
    save_strategy="epoch",
    learning_rate=2e-5,
    load_best_model_at_end=True,
)

# ============================================
# 6. Создать Trainer и начать обучение
# ============================================

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_train,
    eval_dataset=tokenized_val,
    compute_metrics=compute_metrics,
)

# Начать обучение (закомментировано, так как для этого требуется много времени и GPU)
# print("Start training...")
# trainer.train()

# ============================================
# 7. Используйте обученную модель для предсказания
# ============================================

# Здесь мы напрямую используем исходную модель, чтобы продемонстрировать процесс предсказания
# В реальном использовании следует использовать модель, сохранённую после обучения с помощью trainer

def predict_sentiment(text, model, tokenizer):
    """Предсказать тональность предложения"""
    inputs = tokenizer(
        text,
        return_tensors="pt",
        padding=True,
        truncation=True,
        max_length=64
    )

    model.eval()
    with torch.no_grad():
        outputs = model(**inputs)
        logits = outputs.logits
        probabilities = torch.softmax(logits, dim=-1)
        prediction = torch.argmax(probabilities, dim=-1).item()

    label_map = {0: "Negative", 1: "Positive"}
    confidence = probabilities[0][prediction].item()

    return {
        "text": text,
        "sentiment": label_map[prediction],
        "confidence": confidence,
        "negative_prob": probabilities[0][0].item(),
        "positive_prob": probabilities[0][1].item(),
    }

# Протестируйте несколько предложений
test_texts = [
    "EXAMPLE tutorial is really great!",
    "This product's quality is too poor",
    "The weather is really nice today",
    "Мне не очень нравится этот фильм",
]

print("Проверка предсказания модели (с использованием предварительно обученной bert-base-chinese):")
print("-" * 50)
for text in test_texts:
    # Примечание: здесь напрямую используется исходная модель, без дообучения, результаты приведены только для демонстрации
    result = predict_sentiment(text, model, tokenizer)
    print(f"Текст: {result['text']}")
    print(fТональность: {result['sentiment']})
    print(fУверенность: {result['confidence']:.4f})
    print(fВероятность положительного класса: {result['positive_prob']:.4f})
    print(fВероятность отрицательного класса: {result['negative_prob']:.4f})
    print("-" * 50)

Примечание: приведённый выше код демонстрирует полный конвейер, но на самом деле дообучение BERT требует GPU и длительного времени. В производственных средах также можно рассмотреть использование более лёгких моделей (таких как DistilBERT) или напрямую использовать API большой модели.


Глубокое погружение в серию GPT

GPT (Generative Pre-trained Transformer) пошёл другим путём, отличным от BERT.

Авторегрессионная языковая модель

GPT является «авторегрессионной»: она генерирует слово за словом и на каждом шаге использует все ранее сгенерированные слова для предсказания следующего слова.

Например, чтобы сгенерировать «I like eating apples», процесс выглядит так:

1. Вход: «I», предсказываем, что следующее слово — «like»

2. Вход: «I like», предсказываем, что следующее слово — «eating»

3. Вход: «I like eating», предсказываем, что следующее слово — «apples»

Этот подход естественно подходит для генерации текста.

Архитектура только с декодером

GPT использует только декодер Transformer, тогда как BERT использует только энкодер.

Характерная особенность декодера — «маскированное самовнимание»: каждая позиция может видеть только позиции слева от себя, но не справа.

Это логично, потому что генерация текста идёт слева направо; нельзя подглядывать в слова, которые ещё не сгенерированы.

Открытие законов масштабирования

Самое важное открытие серии GPT — закон масштабирования:Производительность модели имеет степенную зависимость от размера модели, объёма данных и объёма вычислений.。

Проще говоря: чем больше модель, чем больше данных и чем дольше обучение, тем лучше результаты. И это улучшение предсказуемо без явного плато.

Именно поэтому серия GPT становится всё "больше": GPT-1 (117M) → GPT-2 (1.5B) → GPT-3 (175B).


T5 и парадигма Seq2Seq

Существует также третья парадигма: структура Encoder-Decoder, представленная T5.

Унифицированный фреймворк Text-to-Text

Основная идея T5 (Text-to-Text Transfer Transformer) такова:Объединить все задачи NLP в формат "текст-в-текст".。

Например:

Классификация текста: Вход "Classification: Этот фильм отличный" → Выход "Положительный"

Перевод: Вход "Translate English to Chinese: Hello world" → Выход "Hello World"

Суммаризация: Вход "Summary: Это статья о..." → Выход "В этой статье в основном обсуждается..."

Преимущество этого унифицированного фреймворка в том, что одна и та же модель может обрабатывать различные задачи без необходимости менять структуру модели для каждой задачи.

Как работает энкодер-декодер

Энкодер отвечает за понимание входного текста, а декодер — за генерацию выходного текста.

Возьмём перевод в качестве примера:

1. Энкодер читает "Hello world" и создаёт «понятое» представление

2. На основе этого представления декодер генерирует "Hello World" слово за словом

Декодер может «обращать внимание» на разные позиции энкодера — например, при генерации Hello он больше внимания уделяет "Hello", а при генерации "world" — больше внимания уделяет "world".


Сравнение трёх основных парадигм: BERT vs GPT vs T5

Давайте сравним эти три архитектуры с помощью таблицы:

ХарактеристикаBERTGPTT5
АрхитектураТолько энкодерТолько декодерЭнкодер-декодер
ВниманиеДвунаправленный (видит и левый, и правый контекст)Однонаправленный (видит только левый контекст)Энкодер двунаправленный, декодер однонаправленный
Задача предобученияMLM (маскированное языковое моделирование)Авторегрессионное языковое моделированиеПорча спанов
Превосходно справляется сЗадачи понимания: классификация, NER, QAЗадачи генерации: продолжение, диалогЗадачи преобразования: перевод, суммаризация, переписывание
Типичные моделиBERT、RoBERTa、ALBERTGPT-1/2/3/4、ChatGPTT5、BART、mT5
Типичные области примененияАнализ тональности, фильтрация спамаПомощник в написании текстов, чат-ботМашинный перевод, суммаризация документов

Современные большие языковые модели в основном используют архитектуру Decoder-only (путь GPT), потому что она лучше всего показывает себя в генерации и может непрерывно улучшаться благодаря Scaling Laws. Однако две другие архитектуры всё ещё имеют преимущества для конкретных задач.


Классификация текста

Классификация текста — одна из самых распространённых задач NLP и лучшая стартовая задача для начинающих.

Многоклассовая классификация на практике

Многоклассовая классификация означает присвоение тексту метки, и эта метка может иметь несколько вариантов.

Например: классификация новостей (технологии, спорт, развлечения, финансы), классификация тикетов службы поддержки, классификация отзывов о товарах и т. д.

Пример

# ============================================
# Классификация текста на практике: сравнение нескольких подходов
# ============================================

from typing import List, Dict, Any

# ============================================
# Подход 1: традиционное машинное обучение + TF-IDF
# Подходит для небольших наборов данных, высокая интерпретируемость
# ============================================

def traditional_text_classification_demo():
    """Использовать традиционные методы для классификации текста"""
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.naive_bayes import MultinomialNB
    from sklearn.pipeline import Pipeline

    # Данные для обучения
    texts = [
        "Сегодня я купил акцию, она сильно выросла",
        "Регулярные инвестиции в фонды — хороший способ управления финансами",
        "Нападающий этой команды забил гол",
        "Плей-офф НБА действительно захватывающий",
        "Камера этого телефона делает отличные фотографии",
        "Уроки программирования от EXAMPLE очень понятны",
        "Сюжет этого фильма очень трогательный",
        "Песни на том альбоме очень приятные",
    ]

    labels = ["Финансы", "Финансы", "Спорт", "Спорт", "Технологии", "Технологии", "Развлечения", "Развлечения"]

    # Создать конвейер: TF-IDF + наивный байесовский классификатор
    pipeline = Pipeline([
        ("tfidf", TfidfVectorizer()),  # Преобразовать текст в TF-IDF признаки
        ("classifier", MultinomialNB()),  # Наивный байесовский классификатор
    ])

    # Обучить
    pipeline.fit(texts, labels)

    #
    test_texts = [
        "Акции упали, у меня плохое настроение",
        "Победный бросок в баскетбольном матче на последней секунде",
        "У недавно выпущенного ноутбука высокая производительность",
        "Новое руководство EXAMPLE по NLP",
    ]

    predictions = pipeline.predict(test_texts)
    probabilities = pipeline.predict_proba(test_texts)

    print("Вариант 1: Традиционное машинное обучение + TF-IDF")
    print("-" * 50)
    for text, pred, probs in zip(test_texts, predictions, probabilities):
        print(f"Текст: {text}")
        print(f"Предсказание: {pred}")
        print(f"Вероятности классов: {dict(zip(pipeline.classes_, probs))}")
        print()

# ============================================
# Вариант 2: использовать pipeline Hugging Face
# Подходит для быстрого прототипирования, обучение не требуется
# ============================================

def huggingface_pipeline_demo():
    """Использовать предобученный pipeline Hugging Face"""
    print("Вариант 2: Hugging Face Pipeline")
    print("-" * 50)

    try:
        from transformers import pipeline

        # Использовать pipeline анализа тональности (английский)
        classifier = pipeline("sentiment-analysis")

        test_texts = [
            "I love EXAMPLE tutorials!",
            "This movie is terrible.",
        ]

        results = classifier(test_texts)

        for text, result in zip(test_texts, results):
            print(f"Текст: {text}")
            print(f"Тональность: {result['label']}")
            print(f"Уверенность: {result['score']:.4f}")
            print()

    except Exception as e:
        print(f"Сначала нужно установить transformers: pip install transformers")
        print(f"Ошибка: {e}")

# ============================================
# Вариант 3: Использовать API большой модели (рекомендуется)
# Подходит для продакшн-среды, лучшая производительность
# ============================================

def llm_based_classification_demo():
    """Использовать большую модель для классификации текста (симуляция)"""
    print("Вариант 3: Классификация с помощью большой модели (демонстрационная концепция)")
    print("-" * 50)

    # В реальном использовании вызывайте API, такие как OpenAI/Anthropic
    # Здесь демонстрируется разработка промпта для классификации

    def classify_with_llm(text: str, categories: List[str]) -> Dict[str, Any]:
        """Имитационная функция для классификации с помощью LLM"""
        # Реальный сценарий: вызов LLM API
        # prompt = f"""Пожалуйста, классифицируйте следующий текст и верните только название категории.
        # Текст: {text}
        # Дополнительные категории: {', '.join(categories)}"""

        # Выполните здесь простое сопоставление по ключевым словам для имитации
        category_map = {
            "Финансы": ["Акции", "Фонд", "Управление капиталом", "Инвестиции"],
            "Спорт": ["Баскетбол", "Футбол", "Матч", "Гол"],
            "Технологии": ["Телефон", "Компьютер", "Программирование", "Руководство"],
            "Развлечения": ["Фильм", "Музыка", "Альбом", "Песня"],
        }

        for category, keywords in category_map.items():
            if any(keyword in text for keyword in keywords):
                return {
                    "category": category,
                    "confidence": 0.9,
                    "reasoning": f"Содержит ключевые слова: {', '.join([k for k in keywords if k in text])}"
                }

        return {"category": "Другое", "confidence": 0.5}

    test_texts = [
        "Акции выросли, я очень рад",
        "Чемпионат мира по футболу открылся",
        "Новый учебник EXAMPLE по программированию",
    ]

    categories = ["Финансы", "Спорт", "Технологии", "Развлечения", "Другое"]

    for text in test_texts:
        result = classify_with_llm(text, categories)
        print(f"Текст: {text}")
        print(f"Категория: {result['category']}")
        print(f"Уверенность: {result['confidence']:.4f}")
        if "reasoning" in result:
            print(f"Причина: {result['reasoning']}")
        print()

# ============================================
# Запустите демо
# ============================================

if __name__ == "__main__":
    traditional_text_classification_demo()
    print("=" * 50)
    llm_based_classification_demo()

Методы Few-Shot классификации

Few-Shot Learning — это новая возможность, появившаяся благодаря большим моделям: достаточно привести несколько примеров, и модель научится классифицировать.

Например:

Текст: Этот продукт хорошо работает → Метка: Положительный

Текст: Качество слишком плохое → Метка: Негативный

Текст: [ваш новый текст] → Метка: ?

После просмотра этих двух примеров большая модель может понять задачу и дать разумную классификацию для нового текста.

Выбор между большой и малой моделями

Когда какой подход использовать? Вот руководство по выбору:

СценарийРекомендуемый подходОбоснование
Малый объём данных (< 1000 записей)Обучение большой модели на нескольких примерахМалые модели не могут этому научиться; большие модели хорошо обобщают
Большой объём данных (> 10000 записей)Дообучить малую модель (например, BERT)Более низкая стоимость, более быстрый инференс
Нужна быстрая проверкаAPI большой моделиОбучение не требуется, сразу пригодно к использованию
Продакшен-среда, высокая пропускная способностьДообучение малой модели + дистилляцияБыстрый инференс, контролируемые затраты
Категории часто меняютсяПромпт-инжиниринг для большой моделиНе требуется переобучение, достаточно изменить промпт

Распознавание именованных сущностей (NER)

Распознавание именованных сущностей (NER) — это задача выделения конкретных сущностей из текста, таких как имена людей, названия мест и названия организаций.

Фреймворк разметки последовательностей

NER обычно моделируется как задача "разметки последовательностей": каждому слову назначается метка, указывающая, является ли оно частью сущности.

Например, предложение: "Zhang SanIn Google 工do", метки могут быть:

张三 → B-PER (начало имени человека)

in → O (не сущность)

Google → B-ORG (начало названия организации)

Work → O

Формат аннотации BIO

Наиболее распространённый формат аннотации — BIO:

B-XXX: начало сущности

I-XXX: внутри сущности

O: не сущность

Например: "Wang Xiaoming works at Microsoft in Beijing", размечено как:

Wang → B-PER

Сяо → I-PER

Мин → I-PER

в → O

Бэй → B-LOC

Цзин → I-LOC

's → O

Вэй → B-ORG

Жуань → I-ORG

Гун → I-ORG

Сы → I-ORG

на → O

класс → O

Практическое применение

Давайте использовать Hugging Face для NER:

Пример

# ============================================
# Практический NER: использование Hugging Face для распознавания именованных сущностей
# ============================================

def ner_with_huggingface():
    """Используйте предварительно обученную модель NER"""
    print(NER в действии: Hugging Face)
    print("-" * 50)

    try:
        from transformers import pipeline

        # Загрузить конвейер NER
        # NER для английского
        ner_pipeline = pipeline(
            "ner",
            model="dbmdz/bert-large-cased-finetuned-conll03-english",
            grouped_entities=True  # Объединять несколько токенов одной сущности
        )

        test_text = "Джон Смит работает в Google в Нью-Йорке. Он любит руководства EXAMPLE."

        results = ner_pipeline(test_text)

        print(f"Текст: {test_text}")
        print("Распознанные сущности:")
        for entity in results:
            print(f" - {entity['word']}: {entity['entity_group']} (уверенность: {entity['score']:.4f})")
        print()

    except Exception as e:
        print(f"Сначала нужно установить transformers: pip install transformers")
        print(f"Ошибка: {e}")

# ============================================
# NER для китайского: использование regex + ключевых слов (простой сценарий)
# ============================================

def simple_chinese_ner():
    """Простой NER для китайского: использование regex и словаря"""
    import re

    print("Простой NER для китайского: regex + словарь")
    print("-" * 50)

    # Словарь сущностей (в реальных сценариях был бы больше)
    person_names = ["Zhang San", "Li Si", "Wang Xiaoming", "Liu Dehua"]
    organizations = ["Google", "Microsoft", "Alibaba", "Tencent", "EXAMPLE"]
    locations = ["Beijing", "Shanghai", "Shenzhen", "New York", "London"]

    # Шаблоны регулярных выражений
    patterns = {
        "PERSON": "|".join(re.escape(name) for name in person_names),
        "ORG": "|".join(re.escape(org) for org in organizations),
        "LOC": "|".join(re.escape(loc) for loc in locations),
        "PHONE": r"1[3-9]\d{9}",  # Номер телефона
        "EMAIL": r"\w+@\w+\.\w+",  # Электронная почта
    }

    def extract_entities(text: str):
        """Извлечь сущности из текста"""
        entities = []

        for entity_type, pattern in patterns.items():
            for match in re.finditer(pattern, text):
                entities.append({
                    "text": match.group(),
                    "type": entity_type,
                    "start": match.start(),
                    "end": match.end(),
                })

        # Сортировать по позиции
        entities.sort(key=lambda x: x["start"])
        return entities

    #
    test_texts = [
        "Чжан Сань работает в Google, номер телефона 13800138000",
        "Ван Сяомин отправляется в командировку в Microsoft в Пекин",
        "Если у вас есть вопросы, свяжитесь с [email protected]",
        "EXAMPLE — очень хороший сайт для обучения",
    ]

    for text in test_texts:
        entities = extract_entities(text)
        print(f"Текст: {text}")
        if entities:
            print("Распознанные сущности:")
            for ent in entities:
                print(f" - {ent['text']}: {ent['type']}")
        else:
            print("Сущности не распознаны")
        print()

# ============================================
# Большая модель для NER
# ============================================

def ner_with_llm():
    """Демонстрация идеи использования большой модели для NER"""
    print("Большая модель для NER (демонстрационная идея)")
    print("-" * 50)

    def extract_entities_with_llm(text: str):
        """Использование LLM для NER (симуляция)"""
        # Реальный сценарий: составить промпт для вызова LLM
        # prompt = f"""Пожалуйста, извлеките именованные сущности из следующего текста.
        # Возвращайте только формат JSON, включая типы сущностей: PERSON (имя человека), ORG (организация), LOC (местоположение).
        # Текст: {text}"""

        # Здесь мы выполняем простую симуляцию
        entities = []

        # Простое сопоставление ключевых слов для симуляции
        if "Zhang San" in text:
            entities.append({"text": "Zhang San", "type": "PERSON"})
        if "Google" in text:
            entities.append({"text": "Google", "type": "ORG"})
        if "Beijing" in text:
            entities.append({"text": "Beijing", "type": "LOC"})
        if "EXAMPLE" in text:
            entities.append({"text": "EXAMPLE", "type": "ORG"})

        return entities

    test_text = "Zhang San работает в Google в Beijing, и он часто использует EXAMPLE для обучения."
    entities = extract_entities_with_llm(test_text)

    print(f"Текст: {test_text}")
    print("Распознанные сущности:")
    for ent in entities:
        print(f" - {ent['text']}: {ent['type']}")

# ============================================
# Запуск демо
# ============================================

if __name__ == "__main__":
    simple_chinese_ner()
    print("=" * 50)
    ner_with_llm()

Машинный перевод

Машинный перевод — одно из самых классических применений NLP и одно из самых ранних коммерциализированных приложений.

Принципы нейронного машинного перевода

Ранний машинный перевод был основан на правилах, затем стал статистическим, а теперь он полностью нейронный.

Нейронный машинный перевод (NMT) обычно использует архитектуру Encoder-Decoder:

1. Энкодер читает предложение исходного языка и генерирует семантическое представление

2. Декодер генерирует предложение целевого языка на основе семантического представления

Механизм Attention позволяет декодеру «фокусироваться» на разных позициях предложения исходного языка при генерации каждого слова — это ключ к улучшению качества перевода.

Метрика оценки: BLEU Score

Как измерить качество перевода? BLEU (Bilingual Evaluation Understudy) — наиболее часто используемая метрика.

Основная идея BLEU:Чем выше совпадение n-грамм между результатом машинного перевода и результатом перевода человека, тем лучше качество.。

Оценка BLEU находится в диапазоне от 0 до 1, чем выше, тем лучше. В целом:

Ниже 0.1: практически непонятно

0.1-0.3: можно понять общий смысл

0.3-0.5: хорошее качество перевода

Выше 0.5: близко к человеческому переводу

Пример

# ============================================
# Демонстрация расчёта оценки BLEU
# ============================================

def bleu_score_demo():
    """Демонстрация расчёта оценки BLEU"""
    print("Демонстрация оценки BLEU")
    print("-" * 50)

    # Примечание: на практике рекомендуется использовать библиотеку sacrebleu
    # Здесь мы проводим концептуальную демонстрацию

    from collections import Counter
    import math

    def compute_ngrams(tokens, n):
        """Вычислить n-грамму"""
        return [tuple(tokens[i:i+n]) for i in range(len(tokens)-n+1)]

    def simple_bleu(reference: str, candidate: str, max_n: int = 4):
        """Простой расчёт BLEU (концептуальная демонстрация)"""
        ref_tokens = reference.split()
        cand_tokens = candidate.split()

        # Вычислить штраф за краткость
        ref_len = len(ref_tokens)
        cand_len = len(cand_tokens)

        if cand_len == 0:
            return 0.0

        if cand_len <= ref_len:
            bp = math.exp(1 - ref_len / cand_len)
        else:
            bp = 1.0

        # Вычислить точность для каждой n-граммы
        precisions = []
        for n in range(1, max_n + 1):
            ref_ngrams = Counter(compute_ngrams(ref_tokens, n))
            cand_ngrams = Counter(compute_ngrams(cand_tokens, n))

            if not cand_ngrams:
                precisions.append(0.0)
                continue

            # Подсчитать количество совпадений
            matches = 0
            for ngram, count in cand_ngrams.items():
                matches += min(count, ref_ngrams.get(ngram, 0))

            total = sum(cand_ngrams.values())
            precisions.append(matches / total if total > 0 else 0.0)

        # Среднее геометрическое
        if all(p == 0 for p in precisions):
            geo_mean = 0.0
        else:
            # Избежать log(0)
            log_sum = sum(math.log(p + 1e-10) for p in precisions) / max_n
            geo_mean = math.exp(log_sum)

        bleu = bp * geo_mean
        return bleu

    #
    reference = "the cat sat on the mat"
    candidates = [
        "the cat sat on the mat",  # Точное совпадение
        "the cat was on the mat",  # Одно слово отличается
        "the cat sat on a mat",    # Другой артикль
        "a cat sat on the mat",
        "the dog sat on the mat",  # Ошибка в одном слове
        "mat the on sat cat the",  # Порядок слов полностью перепутан
        "hello world",             # Полностью нерелевантно
    ]

    print(fЭталонный перевод: {reference})
    print()
    for candidate in candidates:
        bleu = simple_bleu(reference, candidate)
        print(fПеревод-кандидат: {candidate})
        print(fОценка BLEU: {bleu:.4f})
        print()

    # Пример на китайском
    print(Пример перевода с китайского:)
    print("-" * 30)
    ref = Я люблю есть яблоки
    cand1 = Я люблю есть яблоки
    cand2 = Я обожаю есть яблоки
    cand3 = Яблоки я люблю есть

    print(fЭталон: {ref})
    print(fКандидат 1: {cand1}, BLEU: {simple_bleu(ref, cand1):.4f})
    print(fКандидат 2: {cand2}, BLEU: {simple_bleu(ref, cand2):.4f})
    print(fКандидат 3: {cand3}, BLEU: {simple_bleu(ref, cand3):.4f})


if __name__ == "__main__":
    bleu_score_demo()

Качество перевода в эпоху LLM

После появления больших моделей качество машинного перевода достигло нового уровня.

Традиционные модели перевода обучаются на двуязычных параллельных данных, тогда как большие модели обучаются на огромных объёмах текста и глубже понимают язык.

Преимущества больших моделей в переводе:

1. Лучшее понимание контекста: позволяет справляться с неоднозначностью и многозначностью

2. Управление стилем: можно задавать тон и стиль перевода

3. Согласованность терминологии: можно предоставить глоссарий, чтобы обеспечить единообразный перевод имён собственных

4. Многоязычность: одна модель может переводить между десятками или сотнями языков


Анализ тональности

Анализ тональности — это определение эмоциональной окраски текста: положительной, отрицательной или нейтральной.

Детализированный анализ тональности

Простой анализ тональности — это бинарная классификация (положительная/отрицательная); более детализированным может быть:

1. Оценка тональности: 1-5 звёзд, 1 — самая отрицательная, 5 — самая положительная

2. Классификация тональности: гнев, радость, грусть, удивление и т. д.

3. Аспектный анализ тональности: не только определяет общую тональность, но и анализирует тональность по отношению к конкретным аспектам.

Например: «Еда в этом ресторане вкусная, но обслуживание слишком медленное.»

Общая тональность: от нейтральной до положительной

Тональность на уровне аспектов:

Еда → Положительная

Обслуживание → Отрицательная

Многоязычные модели анализа тональности

Современные модели анализа тональности могут обрабатывать несколько языков без необходимости отдельного обучения для каждого языка.

Распространённые многоязычные модели:

Большие модели, такие как XLM-RoBERTa, mBERT (многоязычный BERT), Llama и Claude.


Суммаризация текста

Суммаризация текста — это процесс сокращения длинного текста с сохранением ключевой информации.

Экстрактивная против абстрактивной суммаризации

Существуют два основных подхода к суммаризации:

Экстрактивная суммаризация: выберите наиболее важные предложения из исходного текста и объедините их.

Преимущества: информация точна и не будет сфабрикована

Недостатки: недостаточно плавное изложение, может содержать избыточную информацию

Абстрактивная суммаризация: позвольте модели «понять» исходный текст, а затем написать резюме своими словами.

Преимущества: плавность, краткость и возможность обобщить оригинал

Недостатки: может «галлюцинировать», выдумывая информацию, которой нет в исходном тексте

В эпоху больших моделей абстрактивная суммаризация стала мейнстримом, но при её использовании необходимо обращать внимание на проблему галлюцинаций.

Метрика оценки ROUGE

Как оценивается качество суммаризации? ROUGE — наиболее часто используемая метрика.

У ROUGE есть несколько вариантов:

ROUGE-1: сопоставление на основе униграмм (отдельных слов)

ROUGE-2: сопоставление на основе биграмм (двух слов)

ROUGE-L: на основе наибольшей общей подпоследовательности

Подобно BLEU, ROUGE также измеряет пересечение между сгенерированными машиной резюме и эталонными резюме, созданными человеком.

Пример

# ============================================
# Суммаризация текста: метрики ROUGE + простая экстрактивная суммаризация
# ============================================

def rouge_demo():
    """Демонстрация метрик ROUGE"""
    print("Демонстрация метрик ROUGE")
    print("-" * 50)

    from collections import Counter

    def compute_rouge_1(reference: str, candidate: str) -> float:
        """Вычислить ROUGE-1 (F1 по униграммам)"""
        ref_tokens = reference.split()
        cand_tokens = candidate.split()

        if not ref_tokens or not cand_tokens:
            return 0.0

        ref_counts = Counter(ref_tokens)
        cand_counts = Counter(cand_tokens)

        # Подсчитать количество совпадений
        matches = 0
        for token, count in cand_counts.items():
            matches += min(count, ref_counts.get(token, 0))

        precision = matches / len(cand_tokens)
        recall = matches / len(ref_tokens)

        if precision + recall == 0:
            return 0.0

        f1 = 2 * precision * recall / (precision + recall)
        return f1

    def compute_lcs(a: list, b: list) -> int:
        """Вычислить длину наибольшей общей подпоследовательности"""
        m, n = len(a), len(b)
        dp = [[0] * (n + 1) for _ in range(m + 1)]

        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if a[i-1] == b[j-1]:
                    dp[i][j] = dp[i-1][j-1] + 1
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])

        return dp[m][n]

    def compute_rouge_l(reference: str, candidate: str) -> float:
        """Вычислить ROUGE-L (F1 по наибольшей общей подпоследовательности)"""
        ref_tokens = reference.split()
        cand_tokens = candidate.split()

        if not ref_tokens or not cand_tokens:
            return 0.0

        lcs_len = compute_lcs(ref_tokens, cand_tokens)

        precision = lcs_len / len(cand_tokens)
        recall = lcs_len / len(ref_tokens)

        if precision + recall == 0:
            return 0.0

        f1 = 2 * precision * recall / (precision + recall)
        return f1

    #
    reference = "Обработка естественного языка — важная область искусственного интеллекта, изучающая взаимодействие между компьютерами и человеческим языком."
    candidates = [
        "Обработка естественного языка — важная область искусственного интеллекта, изучающая взаимодействие между компьютерами и человеческим языком.",  # Точное совпадение
        "Обработка естественного языка изучает взаимодействие между компьютерами и человеческим языком и является важной областью искусственного интеллекта.",  # Порядок изменён
        "Обработка естественного языка — область искусственного интеллекта, изучающая взаимодействие между компьютерами и языком.",  # Сокращённая версия
        "EXAMPLE — это сайт для изучения программирования.",  # Нерелевантно
    ]

    print(f"Эталонная сводка: {reference}")
    print()
    for candidate in candidates:
        rouge1 = compute_rouge_1(reference, candidate)
        rougel = compute_rouge_l(reference, candidate)
        print(f"Сводка кандидата: {candidate}")
        print(f"ROUGE-1: {rouge1:.4f}")
        print(f"ROUGE-L: {rougel:.4f}")
        print()


def simple_extractive_summarization():
    Простая экстрактивная суммаризация: выбор важных предложений на основе частоты слов
    print(Демонстрация простой экстрактивной суммаризации)
    print("-" * 50)

    import re
    from collections import Counter

    def split_sentences(text: str) -> list:
        Простая сегментация китайских предложений
        # Разбить предложения по точкам, восклицательным и вопросительным знакам
        sentences = re.split(r'[。!?]', text)
        # Отфильтровать пустые строки
        sentences = [s.strip() for s in sentences if s.strip()]
        return sentences

    def get_word_frequency(text: str) -> Counter:
        """Вычислить частоту слов (простая демонстрация: имитация с помощью n-грамм на уровне символов)"""
        # В реальных сценариях следует использовать инструмент сегментации слов, такой как jieba
        # Здесь мы просто используем униграмму
        chars = [c for c in text if c.strip()]
        return Counter(chars)

    def sentence_score(sentence: str, word_freq: Counter) -> float:
        """Вычислить оценки предложений"""
        if not sentence:
            return 0.0

        # Простая оценка: сумма частот слов в предложении / длина предложения
        total_score = 0.0
        for char in sentence:
            total_score += word_freq.get(char, 0)

        # Нормализация
        return total_score / (len(sentence) + 1)

    def extractive_summary(text: str, num_sentences: int = 2) -> str:
        """Экстрактивная суммаризация"""
        sentences = split_sentences(text)

        if len(sentences) <= num_sentences:
            return text

        # Вычислить частоту слов
        word_freq = get_word_frequency(text)

        # Вычислить оценку для каждого предложения
        scores = [(i, sentence_score(s, word_freq)) for i, s in enumerate(sentences)]

        # Отсортировать по оценке и выбрать лучшие
        scores.sort(key=lambda x: x[1], reverse=True)
        top_indices = [i for i, score in scores[:num_sentences]]
        top_indices.sort()  # Сохранить исходный порядок

        # Объединить результаты
        summary = "。".join(sentences[i] for i in top_indices) + "。"
        return summary

    # Тестовый текст
    test_text = """
Обработка естественного языка — важная отрасль искусственного интеллекта. Она изучает, как научить компьютеры понимать и генерировать человеческий язык.
Эта технология имеет множество применений, включая машинный перевод, анализ тональности, вопросно-ответные системы и многое другое.
В последние годы появление больших языковых моделей привело к крупным прорывам в обработке естественного языка.
Сайт EXAMPLE предлагает множество отличных руководств по программированию, помогающих разработчикам осваивать новые навыки.
"""


    print("Исходный текст:")
    print(test_text.strip())
    print()

    summary = extractive_summary(test_text, num_sentences=2)
    print("Экстрактивное резюме:")
    print(summary)


if __name__ == "__main__":
    rouge_demo()
    print("=" * 50)
    simple_extractive_summarization()
Другие расширения