Классификация токсичных комментариев для интернет-магазина Викишоп

Проект по разработке модели для автоматического обнаружения токсичных комментариев в интернет-магазине Викишоп с использованием методов NLP и машинного обучения.

Классификация токсичных комментариев для интернет-магазина Викишоп

Контекст и цель проекта

Интернет-магазин «Викишоп» запустил новый сервис, позволяющий пользователям редактировать и дополнять описания товаров по принципу вики-сообществ. Клиенты могут предлагать свои правки и комментировать изменения других пользователей. Для поддержания здоровой атмосферы в сообществе магазину потребовался инструмент, который автоматически обнаруживает токсичные комментарии и отправляет их на модерацию.

Задача проекта: Обучить модель классифицировать комментарии на позитивные и негативные (токсичные) с метрикой качества F1 не менее 0.75.

Данные: Набор данных toxic_comments.csv с текстом комментариев и бинарной меткой токсичности.

Архитектура решения

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

Работа с текстовыми данными требует особого подхода к предобработке:

Импорт библиотек и настройка окружения python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import re
import warnings
import numpy as np
import pandas as pd

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import f1_score, classification_report, confusion_matrix
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC

import nltk
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizer
from nltk import pos_tag
from nltk.tokenize import word_tokenize

# Скачивание ресурсов NLP
nltk.download('punkt', quiet=True)
nltk.download('punkt_tab', quiet=True)
nltk.download('averaged_perceptron_tagger', quiet=True)
nltk.download('averaged_perceptron_tagger_eng', quiet=True)
nltk.download('wordnet', quiet=True)
nltk.download('omw-1.4', quiet=True)

warnings.filterwarnings('ignore')
pd.set_option('display.max_colwidth', 120)
RANDOM_STATE = 42

Загрузка и анализ данных

Загрузка данных и первичный анализ python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Загрузка данных
df = pd.read_csv('toxic_comments.csv')

print(f"Размер датасета: {df.shape}")
print(f"Количество токсичных комментариев: {df['toxic'].sum()}")
print(f"Доля токсичных комментариев: {df['toxic'].mean():.2%}")

# Примеры комментариев
print("\nПримеры токсичных комментариев:")
for i, row in df[df['toxic'] == 1].head(3).iterrows():
    print(f"{i}: {row['text'][:100]}...")

print("\nПримеры нетоксичных комментариев:")
for i, row in df[df['toxic'] == 0].head(3).iterrows():
    print(f"{i}: {row['text'][:100]}...")
Проблема несбалансированных данных: В задачах классификации текста часто встречается дисбаланс классов. В нашем случае токсичные комментарии составляют меньшинство, что требует особого подхода к оценке модели.

Предобработка текста

Лемматизация и очистка текста

Для улучшения качества классификации необходимо привести текст к нормальной форме:

Функции предобработки текста python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def get_wordnet_pos(treebank_tag):
    """Преобразование тегов частей речи из Treebank в WordNet."""
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN

def preprocess_text(text):
    """Полная предобработка текста: очистка, токенизация, лемматизация."""
    if not isinstance(text, str):
        return ""
    
    # Очистка текста
    text = text.lower()
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    text = re.sub(r'\s+', ' ', text).strip()
    
    # Токенизация
    tokens = word_tokenize(text)
    
    # Определение частей речи и лемматизация
    lemmatizer = WordNetLemmatizer()
    pos_tags = pos_tag(tokens)
    
    lemmatized_tokens = []
    for token, tag in pos_tags:
        wordnet_pos = get_wordnet_pos(tag)
        lemma = lemmatizer.lemmatize(token, wordnet_pos)
        lemmatized_tokens.append(lemma)
    
    return ' '.join(lemmatized_tokens)

# Применение предобработки ко всему датасету
df['processed_text'] = df['text'].apply(preprocess_text)

Векторизация текста с TF-IDF

TF-IDF (Term Frequency-Inverse Document Frequency) — один из наиболее эффективных методов представления текста в числовом виде:

Векторизация текста python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Разделение данных на обучающую и тестовую выборки
X = df['processed_text']
y = df['toxic']

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

print(f"Обучающая выборка: {X_train.shape[0]} примеров")
print(f"Тестовая выборка: {X_test.shape[0]} примеров")
print(f"Доля токсичных в обучающей: {y_train.mean():.2%}")
print(f"Доля токсичных в тестовой: {y_test.mean():.2%}")

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

Сравнение подходов

Для задачи классификации текста были выбраны две модели:

  1. Logistic Regression — линейная модель, хорошо работающая с разреженными данными
  2. LinearSVC — метод опорных векторов с линейным ядром
Создание и обучение пайплайнов python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Пайплайн для Logistic Regression
lr_pipeline = Pipeline([
    ('tfidf', TfidfVectorizer(
        max_features=5000,
        ngram_range=(1, 2),
        min_df=5,
        max_df=0.7
    )),
    ('clf', LogisticRegression(
        random_state=RANDOM_STATE,
        class_weight='balanced',
        max_iter=1000
    ))
])

# Пайплайн для LinearSVC
svc_pipeline = Pipeline([
    ('tfidf', TfidfVectorizer(
        max_features=5000,
        ngram_range=(1, 2),
        min_df=5,
        max_df=0.7
    )),
    ('clf', LinearSVC(
        random_state=RANDOM_STATE,
        class_weight='balanced',
        max_iter=10000
    ))
])

# Обучение моделей
lr_pipeline.fit(X_train, y_train)
svc_pipeline.fit(X_train, y_train)

Оптимизация гиперпараметров

Для улучшения качества модели был проведен поиск оптимальных гиперпараметров:

Grid Search для оптимизации python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Параметры для Grid Search
param_grid = {
    'tfidf__max_features': [3000, 5000, 10000],
    'tfidf__ngram_range': [(1, 1), (1, 2)],
    'clf__C': [0.1, 1, 10]
}

# Grid Search для Logistic Regression
grid_search_lr = GridSearchCV(
    lr_pipeline,
    param_grid,
    cv=3,
    scoring='f1',
    n_jobs=-1,
    verbose=1
)

grid_search_lr.fit(X_train, y_train)
print(f"Лучшие параметры для Logistic Regression: {grid_search_lr.best_params_}")
print(f"Лучший F1-score: {grid_search_lr.best_score_:.4f}")

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

Метрики классификации

Оценка моделей на тестовой выборке python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def evaluate_model(model, X_test, y_test, model_name):
    """Полная оценка модели с выводом метрик."""
    y_pred = model.predict(X_test)
    
    print(f"\n=== {model_name} ===")
    print(f"F1 Score: {f1_score(y_test, y_pred):.4f}")
    
    print("\nClassification Report:")
    print(classification_report(y_test, y_pred))
    
    # Матрица ошибок
    cm = confusion_matrix(y_test, y_pred)
    print("Confusion Matrix:")
    print(f"True Negatives: {cm[0, 0]}")
    print(f"False Positives: {cm[0, 1]}")
    print(f"False Negatives: {cm[1, 0]}")
    print(f"True Positives: {cm[1, 1]}")
    
    return y_pred

# Оценка обеих моделей
lr_pred = evaluate_model(lr_pipeline, X_test, y_test, "Logistic Regression")
svc_pred = evaluate_model(svc_pipeline, X_test, y_test, "LinearSVC")

Анализ важных признаков

Для интерпретации модели можно проанализировать наиболее важные признаки:

Анализ важности признаков python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def get_top_features(model, vectorizer, n=20):
    """Получение наиболее важных признаков для классификации."""
    feature_names = vectorizer.get_feature_names_out()
    coefficients = model.named_steps['clf'].coef_[0]
    
    # Создание DataFrame с признаками и их весами
    features_df = pd.DataFrame({
        'feature': feature_names,
        'coefficient': coefficients
    })
    
    # Топ признаков для токсичных комментариев
    toxic_top = features_df.nlargest(n, 'coefficient')
    
    # Топ признаков для нетоксичных комментариев
    non_toxic_top = features_df.nsmallest(n, 'coefficient')
    
    return toxic_top, non_toxic_top

# Получение топ признаков для лучшей модели
vectorizer = lr_pipeline.named_steps['tfidf']
toxic_top, non_toxic_top = get_top_features(lr_pipeline, vectorizer, 15)

print("Топ признаков для токсичных комментариев:")
print(toxic_top[['feature', 'coefficient']].to_string(index=False))

print("\nТоп признаков для нетоксичных комментариев:")
print(non_toxic_top[['feature', 'coefficient']].to_string(index=False))

Ключевые особенности проекта

Основные достижения:

  1. Реализован полный пайплайн обработки текста: от очистки до классификации
  2. Достигнута метрика F1-score > 0.75, что превышает требования проекта
  3. Сравнены две модели машинного обучения с подробным анализом результатов
  4. Разработана система интерпретации модели через анализ важных признаков

Вызовы и решения:

  • Проблема: Несбалансированные данные (токсичные комментарии в меньшинстве)
  • Решение: Использование class_weight='balanced' в моделях
  • Проблема: Шум в текстовых данных (орфографические ошибки, сленг)
  • Решение: Комплексная предобработка с лемматизацией и очисткой
  • Проблема: Выбор оптимальных гиперпараметров
  • Решение: Grid Search с кросс-валидацией

Технологический стек

Технология Назначение Версия
Python Основной язык программирования 3.9+
pandas Обработка и анализ данных 1.5+
scikit-learn Машинное обучение и предобработка 1.3+
nltk Обработка естественного языка 3.8+
NumPy Научные вычисления 1.24+
Jupyter Notebook Интерактивная разработка 6.5+
re (регулярные выражения) Очистка текста встроенный

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

Разработанная модель может быть интегрирована в реальную систему модерации:

Система модерации комментариев python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class CommentModerationSystem:
    """Система автоматической модерации комментариев."""
    
    def __init__(self, model, threshold=0.5):
        self.model = model
        self.threshold = threshold
    
    def moderate_comment(self, text):
        """Модерация отдельного комментария."""
        # Предобработка текста
        processed_text = preprocess_text(text)
        
        # Предсказание вероятности токсичности
        if hasattr(self.model, 'predict_proba'):
            proba = self.model.predict_proba([processed_text])[0, 1]
        else:
            # Для LinearSVC используем decision_function
            decision = self.model.decision_function([processed_text])[0]
            proba = 1 / (1 + np.exp(-decision))
        
        # Принятие решения
        is_toxic = proba >= self.threshold
        
        return {
            'text': text,
            'processed_text': processed_text,
            'toxicity_probability': proba,
            'is_toxic': is_toxic,
            'action': 'send_to_moderation' if is_toxic else 'publish',
            'confidence': 'high' if abs(proba - 0.5) > 0.3 else 'medium'
        }
    
    def batch_moderate(self, comments):
        """Пакетная модерация комментариев."""
        results = []
        for comment in comments:
            results.append(self.moderate_comment(comment))
        return pd.DataFrame(results)

# Пример использования
moderation_system = CommentModerationSystem(lr_pipeline)

test_comments = [
    "This product is amazing! Highly recommended!",
    "You are an idiot and your product is garbage.",
    "Could be better, but overall not bad."
]

results = moderation_system.batch_moderate(test_comments)
print(results[['text', 'toxicity_probability', 'action']])

Заключение

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

Дальнейшее развитие:

  1. Эксперименты с более сложными моделями (BERT, RoBERTa)
  2. Добавление мультиязычной поддержки
  3. Интеграция с реальной системой комментариев интернет-магазина
  4. Создание дашборда для мониторинга качества модерации
  5. Реализация обратной связи от модераторов для дообучения модели

Ссылки и ресурсы

Посмотреть полный проект с кодом и подробным анализом можно на GitHub:

GitHub Repository: Wikishop Toxic Comments Classification