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

2. Векторные представления слов
Векторные представления слов позволяют нам преобразовывать слова в числовые векторы. Это существенно улучшает качество моделей NLP.
В данной статье мы рассмотрим:
- Word2Vec 
- GloVe 
- FastText 
- Библиотеку Gensim 
- Примеры кода и задания 
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ификации текста:
- Сбор данных 
- Предобработка данных 
- Преобразование текста в числовой формат 
- Обучение модели 
- Оценка модели 
- Применение модели 
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. Вывод
В ходе данной статьи мы подробно изучили основные концепции и методы, используемые в области обработки естественного языка. Рассмотренные темы охватывают большое количество инструментов и технологий, которые позволяют эффективно работать с текстовыми данными и решать различные задачи анализа и понимания естественного языка. В следующих статьях мы займёмся реализацией небольших проектов. Спасибо за внимание!
 
           
 
ENick
"Активирационные функции: " - это функции активации?
https://habr.com/ru/companies/raft/articles/784964/
ceoofmsc Автор
Спасибо за ваш комментарий!
Да, это функции активации. В статье отредактировал, чтоб было понятнее.