1. Введение

В предыдущих статьях мы рассмотрели теоретические основы NLP, включая базовые понятия, такие как токенизация, стемминг, лемматизация и другие. Мы также поработали с библиотеками NLTK и spaCy и выполнили простые задания по обработке текста.

В этой статье мы продолжим изучение NLP и перейдем к более продвинутым темам, которые являются главными для построения современных приложений и моделей в области обработки естественного языка. А также создадим и обучим модели самостоятельно, используя TensorFlow/Keras и PyTorch.

2. Векторные представления слов

Векторные представления слов позволяют нам преобразовывать слова в числовые векторы. Это существенно улучшает качество моделей NLP.

В данной статье мы рассмотрим:

  1. Word2Vec

  2. GloVe

  3. FastText

  4. Библиотеку Gensim

  5. Примеры кода и задания

2.1 Word2Vec

Word2Vec - это группа моделей, которую разработала команда Google. Word2Vec преобразует слова в векторы определённой размерности, где схожие по смыслу слова имеют близкие векторные представления.

Архитектуры Word2Vec

Word2Vec представляет два основных метода обучения:

  • Continuous Bag-of-Words (CBOW): предсказывает текущее слово по контексту (окружающим словам).

  • Skip-Gram: предсказывает окружающие слова по текущему слову.

Особенности

  • Эффективность: Быстро обучается на больших объёмах текстах.

  • Качество: Учитывает семантические отношения между словами.

2.2 GloVe

Glove - это модель, разработанная в Стэнфордском университете. В отличие от Word2Vec, который фокусируется на локальных связях между словами, а GloVe фокусируется на глобальных связях, проверяя, насколько часто два слова встречаются вместе в тексте.

Особенности

  • Глобальная матрица соотношений - это способ увидеть, как слова связаны между собой в целом по всем текстам, проверяя, как часто они появляются вместе.

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

2.3 FastText

FastText — модель, разработанная Facebook AI Research. Она расширяет Word2Vec, учитывая морфологию слов.

Особенности

  • Символьные n-граммы: Представляет слова как группу буквенных n-грамм.

  • Преимущества: Лучше справляется с редкими словами и опечатками.

2.4 Реализация и применение с помощью Gensim

Gensim — это библиотека Python для для векторных представлений.

Установка Gensim

pip install gensim

2.5 Word2Vec с использованием Gensim

Для начала подготовим наши данные:

import gensim
from gensim.models import Word2Vec
from gensim.utils import simple_preprocess

texts = [
    "Хабр — популярная платформа для IT специалистов",
    "На Хабре можно найти статьи по программированию и технологиям",
    "Пользователи Хабра делятся своим опытом и знаниями",
]

# Предобработка текстов
sentences = [simple_preprocess(text) for text in texts]

Обучим нашу модель Word2Vec:

model = Word2Vec(
    sentences=sentences,
    vector_size=100,  # размерность текстов
    window=5,         # размер контекстного окна
    min_count=1,      # минимальную частоту слова
    workers=4,        # количество потоков
    sg=0              # использование Continuous Bag-of-Words
)

Получение вектора слова:

vector = model.wv['хабр']
print("Вектор слова 'хабр':\n", vector)

Вывод:

Вектор слова 'хабр': [ 0.00973555 -0.00978038 -0.00649949 0.00278379 0.00643199 -0.00536737 0.00275249 0.00912131 -0.00681542 -0.00609991 -0.00498964 -0.00367641 0.00184972 0.00968263 0.00643778 0.00039709 0.00247077 0.00844049 0.00912898 0.00562875 0.00594626 -0.00762069 -0.00382767 -0.00568033 0.00618177 -0.00225645 -0.00877944 0.00761912 0.00839968 -0.00332024 0.00911666 -0.00073836 -0.00362652 -0.00038469 0.00019443 -0.0035049 0.00281324 0.00572971 0.00686901 -0.00890347 -0.00219273 -0.0054818 0.0075211 0.0065017 -0.00436072 0.00232683 -0.00595366 0.0002365 0.00946176 -0.00260984 -0.00518772 -0.00739721 -0.00291194 -0.00086431 0.00352786 0.00974189 -0.00338928 0.00190177 0.00968101 0.00153159 0.0009865 0.00980237 0.00929546 0.00770807 -0.00617053 0.00998399 0.00584899 0.00907267 -0.0019952 0.00334994 0.00683356 -0.00389376 0.00664285 0.00256286 0.00931373 -0.0030358 -0.00310937 0.00621539 -0.00907825 -0.00725399 -0.00650003 -0.00074907 -0.00236302 0.00681552 0.00923659 -0.00090976 0.00141282 0.00202036 -0.0020198 -0.00803434 0.00744105 -0.0042979 0.00457652 0.0090897 0.00304322 0.00313879 0.00406183 -0.00270122 0.00382477 0.00033762]

Поиск наиболее похожих слов:

similar_words = model.wv.most_similar('хабр')
print("Слова, похожие на 'хабр':")
for word, similarity in similar_words:
    print(f"{word}: {similarity}")

Вывод:

Слова, похожие на 'хабр': можно: 0.15923377871513367 популярная: 0.1528114527463913 технологиям: 0.14256368577480316 статьи: 0.1326463520526886 своим: 0.1193675547838211 на: 0.07913301885128021 специалистов: 0.07480262219905853 делятся: 0.059599656611680984 по: 0.03546776622533798 для: 0.03307188302278519

2.5 FastText с использованием Gensim

Обучение модели:

from gensim.models import FastText

ft_model = FastText(
    vector_size=100,
    window=5,
    min_count=1,
    workers=4
)

ft_model.build_vocab(corpus_iterable=sentences)

ft_model.train( 
    corpus_iterable=sentences,
    total_examples=len(sentences),
    epochs=10
)

Поиск похожих слов:

similar_words_ft = ft_model.wv.most_similar('хабр')
print("Слова, похожие на 'хабр':")
for word, similarity in similar_words_ft:
    print(f"{word}: {similarity:.3f}")

Вывод:

Слова, похожие на 'хабр': хабра: 0.514 хабре: 0.421 пользователи: 0.046 найти: 0.045 технологиям: 0.041 специалистов: 0.019 по: 0.016 делятся: 0.010 на: -0.002 статьи: -0.034

3. Классификация текста с помощью scikit-learn

Основные шаги класcификации текста:

  1. Сбор данных

  2. Предобработка данных

  3. Преобразование текста в числовой формат

  4. Обучение модели

  5. Оценка модели

  6. Применение модели

3.1 Работа с размеченными данными

Размеченные данные — это данные, где каждому экземпляру соответствует метка или категория.

Источники размеченных данных

  • Многие наборы данных доступны в библиотеке sklearn.datasets или nltk.

  • Также существуют публичные датасеты. Для этого я советую Kaggle, где обычные пользователи делятся наборами данных.

3.2 Оценка качества моделей

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

  • Точность (Accuracy): Показывает, какую долю всех предсказаний модель сделала правильно.

  • Матрица ошибок (Confusion Matrix): Таблица, которая показывает, сколько раз модель правильно или неправильно предсказала каждый класс.

  • Precision: Определяет сколько из всех примеров, которые модель предсказала как положительные, на самом деле положительные.

  • Recall: Определяет, сколько из всех реальных положительных примеров, модель обнаружила.

  • F1-score: Среднее значение между Precision и Recall.

Валидация моделей

  • Тренировочные и тестовые данные: Разделение данных на обучающую и тестовую выборки.

  • Кросс-валидация: Разделение данных на K подвыборок и обучение модели K раз.

3.3 Примеры кода

Классификация текстов новостей из 20 Newsgroups датасета

Этот набор данных представляет собой коллекцию документов группы новостей. 

Импорт библиотек:

import numpy as np
from sklearn.datasets import fetch_20newsgroups
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

Загрузка данных:

categories = ['rec.sport.baseball', 'rec.sport.hockey', 'talk.politics.mideast', 'sci.space']
newsgroups = fetch_20newsgroups(subset='all', categories=categories)


X = newsgroups.data #тексты
y = newsgroups.target #метки

Разделение на обучающую и тестовую выборки:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Преобразование текста в числовой формат:

tfidf_vectorizer = TfidfVectorizer(stop_words='english', lowercase=True)
X_train_tfidf = tfidf_vectorizer.fit_transform(X_train)
X_test_tfidf = tfidf_vectorizer.transform(X_test)

Обучение модели с использованием байесовского классификатора:

model = MultinomialNB()
model.fit(X_train_tfidf, y_train)

Прогнозирование и оценка модели:

y_pred = model.predict(X_test_tfidf) # Прогнозирование на тестовой выборке

print("Accuracy:", accuracy_score(y_test, y_pred)) # Оценка точности

print(classification_report(y_test, y_pred, target_names=newsgroups.target_names))

Вывод:

Подробный отчет
Подробный отчет

Построение матрицы ошибок:

import matplotlib.pyplot as plt
import seaborn as sns

cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d',
            xticklabels=newsgroups.target_names,
            yticklabels=newsgroups.target_names)
plt.ylabel('Истинный класс')
plt.xlabel('Предсказанный класс')
plt.show()

Вывод:

Матрица ошибок
Матрица ошибок

4. Нейронные сети для NLP

Нейронные сети — это мощный инструмент для решения сложных задач в различных сферах. Они постоянно готовы адаптироваться и обучаться, что делает их очень полезными для работы с большими данными.

Основные компоненты

  • Входной слой: Получает данные.

  • Скрытые слои: Обрабатывают эти данные, выявляя в них важные особенности.

  • Выходной слой: Выдаёт результат — ответ или предсказание.

  • Функции активации: Позволяют нейронной сети понимать и представлять сложные, нелинейные зависимости в данных.

  • Функция потерь: Показывает, насколько хороша модель, сравнивая предсказания с реальными данными.

  • Оптимизаторы: Это алгоритмы, которые помогают модели учиться на своих ошибках, корректируя параметры для улучшения точности.

Процесс обучения

  • Прямое распространение (forward propagation): Процесс, при котором входные данные проходят через нейронную сеть от входного слоя к выходному, чтобы сделать предсказание.

  • Обратное распространение (backpropagation): Алгоритм обучения нейронной сети, при котором сеть корректирует свои веса, чтобы уменьшить ошибку между предсказанием и реальным ответом.

4.2 Рекуррентные нейронные сети (RNN)

Рекуррентные нейронные сети — это модели, которые могут помнить предыдущие данные при обработке последовательностей. Они используют информацию из прошлого для улучшения понимания настоящего и будущего элементов.

Проблемы стандартных RNN

  • Ванишинг градиент (исчезающий градиент): при обучении на длинных последовательностях градиенты могут становиться слишком малыми, затрудняя обучение.

  • Эффективность на коротких последовательностях: стандартные RNN хорошо работают с короткими последовательностями, но плохо на длинных.

4.3 LSTM и GRU

LSTM (Long Short-Term Memory) — это разновидность RNN, разработанная для решения проблемы исчезающего градиента.

Компоненты LSTM

  • Входные ворота: Контролируют, какую новую информацию добавить в текущее состояние памяти нейрона в LSTM-сети.

  • Забывающие ворота: Контролируют, какую информацию удалить из состояния памяти.

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

GRU (Gated Recurrent Unit) — упрощенная версия LSTM с меньшим количеством ворот, которая зачастую показывает схожие результаты.

Компоненты GRU

  • Ворот обновления: Контролирует, какую часть информации из памяти сохранить, а какую обновить с новой информацией.

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

4.4 Трансформеры (BERT, GPT)

Трансформеры — это мощные модели, которые используют механизм внимания для эффективной обработки последовательностей данных. Они способны фокусироваться на важных частях входной информации, независимо от их позиции, что позволяет им лучше понимать контекст и сложные взаимосвязи.

Архитектура модели -трансформера

Ключевые компоненты

  • Механизм внимания (Attention Mechanism): Позволяет модели определять, на какие части входных данных обратить больше внимания при обработке.

  • Многоголовое внимание (Multi-Head Attention): Это расширение механизма внимания, которое позволяет модели фокусироваться на разных факторах последовательности одновременно.

  • Фидфорвард слои: Это обычные полносвязные нейронные слои, которые применяются к каждому элементу последовательности отдельно.

Преимущества трансформеров

  • Параллельная обработка: позволяет обучать модели быстрее при использовании GPU.

  • Эффективная работа с длинными зависимостями: механизм внимания эффективно обрабатывает связи между отдаленными элементами.

BERT (Bidirectional Encoder Representations from Transformers)

  • BERT — модель на основе трансформеров, обученная на большом объеме текстов.

  • Двухнаправленная: учитывает контекст как слева, так и справа от целевого слова.

  • Применение: задачи NLP, такие как классификация текста, ответы на вопросы и т.д.

GPT (Generative Pre-trained Transformer)

  • GPT — серия моделей от OpenAI, использующих архитектуру трансформеров.

  • Однонаправленная: предсказывает следующее слово на основе предыдущих.

  • GPT o1, GPT 4, GPT 3: мощные модели, способные генерировать связный и осмысленный текст.

5. Реализация простых моделей с использованием TensorFlow/Keras

Установка библиотек:

pip install tensorflow

Импорт библиотек:

import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import SimpleRNN, Dense, Embedding
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer

Подготовка данных:

texts = [
    "Хабр — популярная платформа для IT-специалистов",  
    "На Хабре можно найти статьи по программированию и технологиям",  
    "Пользователи Хабра делятся своим опытом и знаниями",  
    "Хабр помогает разработчикам узнавать о новых технологиях",  
    "Не все статьи на Хабре полезны", 
    "Иногда на Хабре сложно найти нужную информацию",  
    "Хабр может быть сложен для понимания новичками",  
    "Некоторые статьи на Хабре слишком технически сложны"
]

labels = [1, 1, 1, 1, 0, 0, 0, 0] # Метки (0 - негативный, 1 - позитивный)

labels = np.array(labels, dtype='float32') # Преобразуем метки в numpy.ndarray

Токенизация текста:

tokenizer = Tokenizer()
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index # Словарь
print("Размер словаря:", len(word_index))

Вывод:

Размер словаря: 28

Паддинг последовательностей:

maxlen = 10
data = pad_sequences(sequences, maxlen=maxlen)

Создание модели:

model = Sequential()
model.add(Embedding(input_dim=len(word_index) + 1, output_dim=32))
model.add(SimpleRNN(32))
model.add(Dense(1, activation='sigmoid'))

Компиляция модели:

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

Обучение модели:

model.fit(data, labels, epochs=10)

Вывод:

Точность обучения
Точность обучения

Давайте проверим, как работает обученная модель на новых данных.

Подготовим новые тексты для проверки:

test_texts = [
    "Я нашёл полезную статью на Хабре о машинном обучении",       
    "На Хабре слишком много сложной информации для меня",          
    "Статьи на Хабре помогают мне развиваться как программисту",   
    "Иногда Хабр бывает непонятен новичкам в IT",                  
    "Обожаю читать обзоры новых технологий на Хабре",              
    "На Хабре мало информации по интересующей меня теме",          
    "Хабр - отличный ресурс для IT-специалистов",                 
    "Сложно разобраться в статьях на Хабре без подготовки",        
]

Преобразуем новые тексты так же, как и обучающие данные:

test_sequences = tokenizer.texts_to_sequences(test_texts) # Преобразуем тексты в последовательности
test_data = pad_sequences(test_sequences, maxlen=maxlen)

print("Преобразованные тестовые последовательности:")
print(test_data)

Вывод:

Сделаем предсказания на новых данных:

predictions = model.predict(test_data)

Вывод:

Выведем результаты предсказаний:

threshold = 0.5 # Порог классификации

for i, text in enumerate(test_texts):
    prediction = predictions[i][0]
    predicted_label = "Позитивный" if prediction >= threshold else "Негативный"
    print(f"Текст: {text}")
    print(f"Предсказание: {prediction:.4f}")
    print(f"Класс: {predicted_label}")
    print("-" * 50)

Вывод:

6. Теперь рассмотрим реализацию LSTM c использованием PyTorch

Установка библиотек:

pip install torch

Импорт библиотек:

import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, Dataset
from tensorflow.keras.preprocessing.text import Tokenizer

Подготовка данных:

texts = [
    "Хабр — популярная платформа для IT-специалистов",
    "На Хабре можно найти статьи по программированию и технологиям",
    "Пользователи Хабра делятся своим опытом и знаниями",
    "Хабр помогает разработчикам узнавать о новых технологиях",
    "Не все статьи на Хабре полезны",
    "Иногда на Хабре сложно найти нужную информацию",
    "Хабр может быть сложен для понимания новичками",
    "Некоторые статьи на Хабре слишком технически сложны"
]

labels = [1, 1, 1, 1, 0, 0, 0, 0]
labels = np.array(labels, dtype='float32') # Преобразуем метки в массив NumPy
maxlen = 10 # Параметр максимальной длины последовательности

tokenizer = Tokenizer() # Токенизация текста
tokenizer.fit_on_texts(texts)
word_index = tokenizer.word_index
print("Размер словаря:", len(word_index))

Определение класса Dataset:

class TextDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, maxlen):
        self.texts = texts
        self.labels = labels
        self.tokenizer = tokenizer
        self.maxlen = maxlen

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):   
        text = self.texts[idx] #Получаем текст
        label = self.labels[idx] #Получаем метку

        sequence = self.tokenizer.texts_to_sequences([text])[0] # Преобразуем текст в последовательность токенов

        if len(sequence) < self.maxlen: # Применяем паддинг или обрезку последовательности
            sequence = np.pad(sequence, (0, self.maxlen - len(sequence)), 'constant')
        else:
            sequence = sequence[:self.maxlen]

        sequence = torch.tensor(sequence, dtype=torch.long)  # Преобразуем в тензоры PyTorch
        label = torch.tensor(label, dtype=torch.float32)

        return sequence, label

Определение модели LSTM:

class LSTMModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        super(LSTMModel, self).__init__()
        self.embedding = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)
        self.lstm = nn.LSTM(input_size=embedding_dim, hidden_size=hidden_dim, batch_first=True)
        self.linear = nn.Linear(hidden_dim, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x): # x имеет размерность (batch_size, maxlen)
        embeds = self.embedding(x)         # embeds имеет размерность (batch_size, maxlen, embedding_dim)
        lstm_out, (hn, cn) = self.lstm(embeds)
        out = self.linear(hn[-1]) #Используем скрытый слой
        out = self.sigmoid(out)
        return out

Подготовка DataLoader и модели:

from torch.utils.data import DataLoader

dataset = TextDataset(texts, labels, tokenizer, maxlen) # Создаём датасет
dataloader = DataLoader(dataset, batch_size=2, shuffle=True) # Создаём загрузчик данных

vocab_size = len(word_index) + 1  
embedding_dim = 32
hidden_dim = 32

model = LSTMModel(vocab_size, embedding_dim, hidden_dim) # Инициализируем модель
criterion = nn.BCELoss() # Инициализируем функцию потерь
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # Инициализируем оптимизатор

Обучение модели:

model.train()

num_epochs = 10

for epoch in range(num_epochs):
    total_loss = 0
    for sequences, labels_batch in dataloader:
        optimizer.zero_grad()
        outputs = model(sequences) # Прямой проход
        outputs = outputs.squeeze()
        loss = criterion(outputs, labels_batch) # Вычисляем потерю
        loss.backward() # Обратный проходоптимизация
        optimizer.step() # Оптимизация
        total_loss += loss.item()
    avg_loss = total_loss / len(dataloader)
    print(f"Эпоха {epoch+1}, Потеря: {avg_loss:.4f}")

Вывод:

Проверим модель на новых данных:

test_texts = [
    "Хабр предоставляет отличные возможности для обучения",  
    "Некоторые статьи на Хабре трудно понять",               
]

test_sequences = [] # Преобразуем тексты в последовательности
for text in test_texts:
    sequence = tokenizer.texts_to_sequences([text])[0]
    if len(sequence) < maxlen:
        sequence = np.pad(sequence, (0, maxlen - len(sequence)), 'constant')
    else:
        sequence = sequence[:maxlen]
    test_sequences.append(sequence)

test_sequences = torch.tensor(test_sequences, dtype=torch.long) # Преобразуем в тензор

model.eval() # Переводим модель в режим оценки

with torch.no_grad():
    outputs = model(test_sequences)
    predictions = outputs.squeeze().numpy()

threshold = 0.5

for i, text in enumerate(test_texts):
    prediction = predictions[i]
    predicted_label = "Позитивный" if prediction >= threshold else "Негативный"
    print(f"Текст: {text}")
    print(f"Предсказание: {prediction:.4f}")
    print(f"Класс: {predicted_label}")
    print("-" * 50)

Вывод:

7. Вывод

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

Комментарии (2)


  1. ENick
    09.12.2024 07:14

    "Активирационные функции: " - это функции активации?

    https://habr.com/ru/companies/raft/articles/784964/


    1. ceoofmsc Автор
      09.12.2024 07:14

      Спасибо за ваш комментарий!

      Да, это функции активации. В статье отредактировал, чтоб было понятнее.