← Назад к вопросам
NLP: Классификация текстов
2.4 Senior🔥 151 комментариев
#NLP и обработка текста#Машинное обучение
Условие
Вам нужно построить модель для классификации отзывов клиентов по тональности (положительный/отрицательный/нейтральный).
Опишите:
- Как предобработать текст
- Какие методы векторизации использовать
- Какие модели попробовать (классические ML vs BERT)
- Как оценить качество
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
1. Предобработка текста
import pandas as pd
import numpy as np
import re
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import SnowballStemmer, WordNetLemmatizer
import nltk
# Загрузить необходимые ресурсы
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')
# Загрузка данных
df = pd.read_csv('reviews.csv')
print(f"Размер датасета: {df.shape}")
print(f"\nПримеры отзывов:")
print(df['text'].head())
# === ШАГ 1: БАЗОВАЯ ОЧИСТКА ===
def preprocess_text(text):
# Приведение к нижнему регистру
text = text.lower()
# Удаление URL
text = re.sub(r'http\S+|www.\S+', '', text)
# Удаление email
text = re.sub(r'\S+@\S+', '', text)
# Удаление специальных символов (сохраняем букву, цифру, пробел)
text = re.sub(r'[^a-zA-Zа-яА-Я0-9\s]', '', text)
# Удаление лишних пробелов
text = re.sub(r'\s+', ' ', text).strip()
return text
df['text_clean'] = df['text'].apply(preprocess_text)
print(f"\nПосле базовой очистки:")
print(df['text_clean'].head())
# === ШАГ 2: ТОКЕНИЗАЦИЯ ===
def tokenize_text(text):
tokens = word_tokenize(text)
return tokens
df['tokens'] = df['text_clean'].apply(tokenize_text)
print(f"\nПример токенизации:")
print(f"'{df['text_clean'].iloc[0]}' →")
print(df['tokens'].iloc[0][:20])
# === ШАГ 3: УДАЛЕНИЕ STOPWORDS (СЛУЖЕБНЫЕ СЛОВА) ===
stop_words = set(stopwords.words('english'))
def remove_stopwords(tokens):
return [token for token in tokens if token not in stop_words and len(token) > 2]
df['tokens_no_stop'] = df['tokens'].apply(remove_stopwords)
print(f"\nПосле удаления stopwords:")
print(df['tokens_no_stop'].iloc[0][:20])
# === ШАГ 4: ЛЕММАТИЗАЦИЯ (ПРИВЕДЕНИЕ К БАЗОВОЙ ФОРМЕ) ===
lemmatizer = WordNetLemmatizer()
def lemmatize_tokens(tokens):
return [lemmatizer.lemmatize(token, pos='v') for token in tokens]
df['tokens_lemma'] = df['tokens_no_stop'].apply(lemmatize_tokens)
print(f"\nПосле лемматизации:")
print(df['tokens_lemma'].iloc[0][:20])
# === ШАГ 5: СТЕММИНГ (АЛЬТЕРНАТИВА ЛЕММАТИЗАЦИИ) ===
stemmer = SnowballStemmer('english')
def stem_tokens(tokens):
return [stemmer.stem(token) for token in tokens]
df['tokens_stem'] = df['tokens_no_stop'].apply(stem_tokens)
print(f"""
=== ЛЕММАТИЗАЦИЯ VS СТЕММИНГ ===
Лемматизация:
+ Точнее (использует словари)
+ Результат понятнее
- Медленнее
Пример: running → run, better → good
Стемминг:
+ Быстрее (просто обрезает суффиксы)
- Менее точен
Пример: running → runn, better → better
Выбор: Для продакшена обычно лемматизация
""")
2. Методы векторизации
2.1 Bag of Words (BoW)
from sklearn.feature_extraction.text import CountVectorizer
bow_vectorizer = CountVectorizer(
max_features=5000, # топ 5000 слов
min_df=2, # минимум в 2 документах
max_df=0.8, # максимум в 80% документов
stop_words='english',
ngram_range=(1, 2) # uni и bigrams
)
X_bow = bow_vectorizer.fit_transform(df['text_clean'])
print(f"\nBag of Words:")
print(f" Форма матрицы: {X_bow.shape} (документы x признаки)")
print(f" Разреженность: {1 - X_bow.nnz / (X_bow.shape[0] * X_bow.shape[1]):.2%}")
print(f" Топ 10 слов: {bow_vectorizer.get_feature_names_out()[:10]}")
2.2 TF-IDF
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer(
max_features=5000,
min_df=2,
max_df=0.8,
stop_words='english',
ngram_range=(1, 2),
sublinear_tf=True # применяет log(TF)
)
X_tfidf = tfidf_vectorizer.fit_transform(df['text_clean'])
print(f"\nTF-IDF:")
print(f" TF (Term Frequency): как часто слово в документе")
print(f" IDF (Inverse Document Frequency): редкость слова во всех документах")
print(f" TF-IDF = TF * IDF (важные и редкие слова получают больший вес)")
print(f"\n Форма матрицы: {X_tfidf.shape}")
print(f" Разреженность: {1 - X_tfidf.nnz / (X_tfidf.shape[0] * X_tfidf.shape[1]):.2%}")
2.3 Word Embeddings (Word2Vec)
from gensim.models import Word2Vec
# Обучение Word2Vec на текстах
sentences = df['tokens_lemma'].tolist()
w2v_model = Word2Vec(
sentences=sentences,
vector_size=100, # размерность вектора
window=5, # контекстное окно
min_count=2, # минимум вхождений слова
workers=4,
sg=1 # Skip-gram (лучше для малых датасетов)
)
print(f"\nWord2Vec:")
print(f" Размерность вектора: {w2v_model.vector_size}")
print(f" Словарь: {len(w2v_model.wv)} слов")
print(f" Пример: вектор для 'good': {w2v_model.wv['good'][:5]}...")
# Усреднение векторов слова в документ
def get_w2v_vector(tokens, model, vector_size=100):
vectors = [model.wv[token] for token in tokens if token in model.wv]
if vectors:
return np.mean(vectors, axis=0)
return np.zeros(vector_size)
X_w2v = np.array([get_w2v_vector(tokens, w2v_model) for tokens in df['tokens_lemma']])
print(f"\n Матрица документов: {X_w2v.shape}")
2.4 Pre-trained Word Embeddings (GloVe, FastText)
# Загрузка GloVe (требует предварительной загрузки файла)
# Альтернатива: использовать gensim.downloader
import gensim.downloader as api
# Загружаем предобученные embeddings
glove_vectors = api.load('glove-wiki-gigaword-300')
print(f"\nPre-trained GloVe:")
print(f" Размер словаря: {len(glove_vectors)}")
print(f" Размерность: 300")
print(f" Пример синонимов 'good': {glove_vectors.most_similar('good', topn=5)}")
3. Модели классификации
3.1 Классические ML модели
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import lightgbm as lgb
# Кодирование целевой переменной
label_mapping = {'negative': 0, 'neutral': 1, 'positive': 2}
df['label_encoded'] = df['label'].map(label_mapping)
# Разделение данных
X_train, X_test, y_train, y_test = train_test_split(
X_tfidf, df['label_encoded'], test_size=0.2, random_state=42, stratify=df['label_encoded']
)
print(f"\nTrain: {X_train.shape}, Test: {X_test.shape}")
print(f"Распределение классов (train): {np.bincount(y_train)}")
# === МОДЕЛЬ 1: Логистическая регрессия ===
lr_model = LogisticRegression(
max_iter=1000,
class_weight='balanced',
C=1.0, # регуляризация
random_state=42
)
lr_model.fit(X_train, y_train)
y_pred_lr = lr_model.predict(X_test)
print(f"\n=== LOGISTIC REGRESSION ===")
print(f" Accuracy: {accuracy_score(y_test, y_pred_lr):.4f}")
print(f" Precision: {precision_score(y_test, y_pred_lr, average='weighted'):.4f}")
print(f" Recall: {recall_score(y_test, y_pred_lr, average='weighted'):.4f}")
print(f" F1-Score: {f1_score(y_test, y_pred_lr, average='weighted'):.4f}")
# === МОДЕЛЬ 2: Random Forest ===
rf_model = RandomForestClassifier(
n_estimators=100,
max_depth=15,
class_weight='balanced',
random_state=42,
n_jobs=-1
)
rf_model.fit(X_train, y_train)
y_pred_rf = rf_model.predict(X_test)
print(f"\n=== RANDOM FOREST ===")
print(f" Accuracy: {accuracy_score(y_test, y_pred_rf):.4f}")
print(f" F1-Score: {f1_score(y_test, y_pred_rf, average='weighted'):.4f}")
# === МОДЕЛЬ 3: LightGBM (обычно лучше) ===
lgb_model = lgb.LGBMClassifier(
n_estimators=100,
learning_rate=0.05,
max_depth=7,
num_leaves=31,
class_weight='balanced',
random_state=42
)
lgb_model.fit(X_train, y_train)
y_pred_lgb = lgb_model.predict(X_test)
print(f"\n=== LIGHTGBM ===")
print(f" Accuracy: {accuracy_score(y_test, y_pred_lgb):.4f}")
print(f" F1-Score: {f1_score(y_test, y_pred_lgb, average='weighted'):.4f}")
# === МОДЕЛЬ 4: SVM ===
svm_model = LinearSVC(
C=1.0,
class_weight='balanced',
max_iter=2000,
random_state=42
)
svm_model.fit(X_train, y_train)
y_pred_svm = svm_model.predict(X_test)
print(f"\n=== LINEAR SVM ===")
print(f" Accuracy: {accuracy_score(y_test, y_pred_svm):.4f}")
print(f" F1-Score: {f1_score(y_test, y_pred_svm, average='weighted'):.4f}")
3.2 BERT (Transformers)
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
print(f"\n=== BERT (TRANSFORMERS) ===")
# Загрузка предобученной модели
model_name = 'distilbert-base-uncased-finetuned-sst-2-english'
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
# Примеры
test_texts = [
"This product is amazing! I love it.",
"Terrible quality, waste of money.",
"It's okay, nothing special."
]
print(f"\nПримеры BERT прогнозов:")
for text in test_texts:
inputs = tokenizer(text, return_tensors='pt')
outputs = model(**inputs)
logits = outputs.logits
predicted_class = logits.argmax().item()
sentiment = ['negative', 'positive'][predicted_class]
confidence = torch.softmax(logits, dim=1).max().item()
print(f" '{text}' → {sentiment} ({confidence:.2%})")
print(f"""
=== FINE-TUNING BERT НА СВОИХ ДАННЫХ ===
Шаги:
1. Подготовить данные (текст + метка)
2. Токенизировать с помощью tokenizer
3. Создать DataLoader
4. Обучить модель с small learning rate (2e-5)
5. Сохранить fine-tuned модель
Время обучения: ~30 минут на GPU для 10k примеров
Точность: обычно 85-95% для 3-class sentiment
""")
3.3 Сравнение подходов
| Метод | БоW+LR | TF-IDF+LGB | Word2Vec+NN | BERT |
|---|---|---|---|---|
| Скорость обучения | Очень быстро | Быстро | Среднее | Медленно |
| Точность | 75% | 82% | 80% | 90%+ |
| Требуемые данные | 100 | 500 | 1000 | 1000+ |
| Требуемо GPU | Нет | Нет | Да | Да |
| Интерпретация | Отличная | Хорошая | Средняя | Плохая |
Рекомендация: Начните с TF-IDF + LightGBM (быстро и точно), потом fine-tune BERT если нужна еще точность.
4. Оценка качества
from sklearn.metrics import (
confusion_matrix,
classification_report,
roc_auc_score,
roc_curve
)
import matplotlib.pyplot as plt
import seaborn as sns
# === CONFUSION MATRIX ===
cm = confusion_matrix(y_test, y_pred_lgb)
fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax,
xticklabels=['negative', 'neutral', 'positive'],
yticklabels=['negative', 'neutral', 'positive'])
ax.set_title('Confusion Matrix')
ax.set_ylabel('True Label')
ax.set_xlabel('Predicted Label')
plt.tight_layout()
plt.savefig('confusion_matrix.png')
# === CLASSIFICATION REPORT ===
print(f"\n=== CLASSIFICATION REPORT ===")
print(classification_report(
y_test, y_pred_lgb,
target_names=['negative', 'neutral', 'positive']
))
# === МЕТРИКИ ПО КЛАССАМ ===
from sklearn.metrics import precision_recall_fscore_support
precision, recall, f1, _ = precision_recall_fscore_support(
y_test, y_pred_lgb, labels=[0, 1, 2]
)
for i, label in enumerate(['negative', 'neutral', 'positive']):
print(f"\n{label.upper()}:")
print(f" Precision: {precision[i]:.4f} (из предсказанных, сколько верно)")
print(f" Recall: {recall[i]:.4f} (из реальных, сколько поймали)")
print(f" F1-Score: {f1[i]:.4f} (гармоническое среднее)")
print(f"""
=== ИНТЕРПРЕТАЦИЯ МЕТРИК ===
Accuracy: (TP + TN) / Total
- Доля верных предсказаний
- Может быть обманчива при дисбалансе классов
- Целевое значение: > 85%
Precision: TP / (TP + FP)
- Из всех положительных предсказаний, сколько верны
- Важна когда дорого false positive (например, spam)
- Целевое значение: > 80%
Recall: TP / (TP + FN)
- Из всех реальных положительных, сколько найти
- Важна когда дорого false negative (упустить положительный)
- Целевое значение: > 80%
F1-Score: 2 * (Precision * Recall) / (Precision + Recall)
- Гармоническое среднее Precision и Recall
- Лучше использовать при дисбалансе классов
- Целевое значение: > 0.80
""")