<jupyter_start><jupyter_text>Модель определения токсичных комментариев**Проект №12 Яндекс.Практикум - Data Science**  Описание проекта**Исходные данные:**Интернет-магазин «Викишоп» запускает новый сервис. Теперь пользователи могут редактировать и дополнять описания товаров, как в вики-сообществах. То есть клиенты предлагают свои правки и комментируют изменения других. Магазину нужен инструмент, который будет искать токсичные комментарии и отправлять их на модерацию.В вашем распоряжении набор данных с разметкой о токсичности правок.**Цель проекта:**Разработать модель классификации комментариев на позитивные и негативные.**Условия задачи:**Значение метрики качества **F1** не меньше **0.75**.   Структура проекта* [1. Загрузка и анализ данных](start)* [2. Подготовка данных](preparation)* [3. Обучение модели](model)* [4. Тестирование](testing)* [5. Выводы](conclusion)  1. Загрузка и анализ данных  Импортируем необходимые библиотеки<jupyter_code>import re
import nltk
import string
import spacy
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import f1_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from nltk.corpus import stopwords 
from textblob import TextBlob, Word
nltk.download('stopwords')
nltk.download('wordnet')
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')<jupyter_output>[nltk_data] Downloading package stopwords to
[nltk_data]     C:\Users\Sergio\AppData\Roaming\nltk_data...
[nltk_data]   Package stopwords is already up-to-date!
[nltk_data] Downloading package wordnet to
[nltk_data]     C:\Users\Sergio\AppData\Roaming\nltk_data...
[nltk_data]   Package wordnet is already up-to-date!
[nltk_data] Downloading package punkt to
[nltk_data]     C:\Users\Sergio\AppData\Roaming\nltk_data...
[nltk_data]   Package punkt is already up-to-date!
[nltk_data] Downloading package averaged_perceptron_tagger to
[nltk_data]     C:\Users\Sergio\AppData\Roaming\nltk_data...
[nltk_data]   Package averaged_perceptron_tagger is already up-to-
[nltk_data]       date!<jupyter_text>Загрузим данные в DataFrame<jupyter_code>dataset = 'toxic_comments.csv'

try:
    data = pd.read_csv(f'../datasets/{dataset}', sep=',')
    print(f'Прочитан файл с данными: "./datasets/{dataset}"')
except:
    try:
        data = pd.read_csv(f'/datasets/{dataset}', sep=',') # yandex.praktikum
        print(f'Прочитан файл с данными: "/datasets/{dataset}"')
    except Exception as err:
        print(repr(err))
data.head(5)
data.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 159571 entries, 0 to 159570
Data columns (total 2 columns):
 #   Column  Non-Null Count   Dtype 
---  ------  --------------   ----- 
 0   text    159571 non-null  object
 1   toxic   159571 non-null  int64 
dtypes: int64(1), object(1)
memory usage: 2.4+ MB<jupyter_text>Описание данных:* text - текст комментария* toxic — целевой признак<jupyter_code>print(f"Кол-во текстов класса 0: {data['toxic'].value_counts()[0]} ({(data['toxic'].value_counts()[0]/data.shape[0])*100:.2f}%)")
print(f"Кол-во текстов класса 1: {data['toxic'].value_counts()[1]} ({(data['toxic'].value_counts()[1]/data.shape[0])*100:.2f}%)")<jupyter_output>Кол-во текстов класса 0: 143346 (89.83%)
Кол-во текстов класса 1: 16225 (10.17%)<jupyter_text>Выводы:* отсутствуют пропуски в данных* классы несбалансированы, учтём это при обучении моделей  2. Подготовка данных Лемматиизируем текст с помощью библиотек **spacy** и **TextBlob** Реализуем функции лемматизации<jupyter_code># TextBlob lemmatizer
def lemmatize_tblob(text):
    sentence = TextBlob(text)
    tag_dict = {"J": 'a', 
                "N": 'n', 
                "V": 'v', 
                "R": 'r'}
    words_and_tags = [(w, tag_dict.get(pos[0], 'n')) for w, pos in sentence.tags]    
    lemmatized_list = [wd.lemmatize(tag) for wd, tag in words_and_tags]
    return " ".join(lemmatized_list)
# Spacy lemmatizer
def lemmatize_spacy(text, lemmatizer):
    doc = lemmatizer(text)
    lemm_text = " ".join([token.lemma_ for token in doc])
        
    return lemm_text<jupyter_output><empty_output><jupyter_text>Функция очистки текста (оставляет только буквы, кавычки и пробелы)<jupyter_code>def clear(text):
    cleaned = re.sub(r"[^a-zA-Z\' ]", ' ', text)
    return " ".join(cleaned.split())<jupyter_output><empty_output><jupyter_text>Лемматизируем текст<jupyter_code>%%time

data['lemm_tblob'] = data['text'].apply(lemmatize_tblob)
%%time

sp = spacy.load('en_core_web_sm', disable=['parser', 'ner'])

data['lemm_spacy'] = data['text'].apply(lemmatize_spacy, lemmatizer=sp)<jupyter_output>Wall time: 18min 30s<jupyter_text>Очистим лемматизированных текст от лишних символов<jupyter_code>%%time

data['lemm_tblob'] = data['lemm_tblob'].apply(clear)
data['lemm_spacy'] = data['lemm_spacy'].apply(clear)
data
print(data['lemm_spacy'][4])
print(data['lemm_tblob'][4])
print(data['lemm_spacy'][5])
print(data['lemm_tblob'][5])
print(data['lemm_spacy'][6])
print(data['lemm_tblob'][6])
print(data['lemm_spacy'][9])
print(data['lemm_tblob'][9])
print(data['lemm_spacy'][11])
print(data['lemm_tblob'][11])<jupyter_output>you sir be my hero any chance you remember what page that be on
You sir be my hero Any chance you remember what page that 's on
congratulation from I as well use the tool well talk
Congratulations from me a well use the tool well talk
COCKSUCKER before you pis around on my work
COCKSUCKER BEFORE YOU PISS AROUND ON MY WORK
alignment on this subject and which be contrary to those of DuLithgow
alignment on this subject and which be contrary to those of DuLithgow
bbq be a man and let discuss it maybe over the phone
bbq be a man and let discuss it maybe over the phone<jupyter_text>Выводы:* лемматизировали тексты используя библиотеки **spacy** и **TextBlob*** для **TextBlob** применили технику POS-tag (part-of-speech), в **spacy** она используется по умолчанию* значимых отличий в лемматизированных текстах разными библиотеками не замечено* лемматизация с **spacy** работает незначительно быстрее, чем с **TextBlob*** сравним на обученных моделях есть ли различия в качестве при использования лемматизированных текстов используя **spacy** и **TextBlob**  3. Обучение модели Разделим данные на тестовую и тренировочную выборку в пропорции 4:1<jupyter_code>target = data['toxic']
# Do not use extra space
corpus_train, corpus_test, target_train, target_test = train_test_split(data['lemm_spacy'].values.astype('U'), target, test_size=0.2, stratify=target, random_state=42)<jupyter_output><empty_output><jupyter_text>Создадим матрицу оценки важности слов, рассчитав для каждого слова TF-IDF<jupyter_code>stop_words = set(stopwords.words('english'))
count_vect = TfidfVectorizer(stop_words=stop_words)
tf_idf  = count_vect.fit_transform(corpus_train) 
print("Размер массива TF-IDF:", tf_idf.shape)
target = data['toxic']
# Do not use extra space
corpus_train_tblob, corpus_test_tblob, target_train_tblob, target_test_tblob = train_test_split(data['lemm_tblob'].values.astype('U'), target, test_size=0.2, stratify=target, random_state=42)
stop_words = set(stopwords.words('english'))

count_vect_tblob = TfidfVectorizer(stop_words=stop_words)
tf_idf_tblob  = count_vect_tblob.fit_transform(corpus_train) 
print("Размер массива TF-IDF:", tf_idf_tblob.shape)<jupyter_output>Размер массива TF-IDF: (127656, 138588)<jupyter_text>Random Forrest<jupyter_code>%%time

# Parameters selected on smaller amount of data 
model = RandomForestClassifier(ccp_alpha=0.005,
                               n_jobs=-1)

parameters = {'max_depth' : [None, 30],
              'class_weight' : [None, {0: 1, 1: 5}, {0: 1, 1: 8}]}

clf = GridSearchCV(model, parameters)
clf.fit(tf_idf, target_train)
model_rfc = clf.best_estimator_
model_rfc<jupyter_output>Wall time: 1h 14min<jupyter_text>Проверим F1 и баланс классов на обучающей выборке<jupyter_code>predicted = model_rfc.predict(tf_idf)
f1 = f1_score(target_train, predicted)
print(f1)

unique, counts = np.unique(predicted, return_counts=True)
print(dict(zip(unique, counts)))<jupyter_output>0.08057362507392077
{0: 127108, 1: 548}<jupyter_text>Logistic Regression<jupyter_code>%%time

model = LogisticRegression(solver='lbfgs',
                           n_jobs=-1,
                           random_state=91)

parameters = {'class_weight': [{0: 1, 1: 5}, {0: 1, 1: 7}]}

clf = GridSearchCV(model, parameters)
clf.fit(tf_idf, target_train)
model_logreg = clf.best_estimator_
model_logreg<jupyter_output>Wall time: 49.8 s<jupyter_text>Проверим F1 и баланс классов на обучающей выборке<jupyter_code>predicted = model_logreg.predict(tf_idf)
f1 = f1_score(target_train, predicted)
print(f1)

unique, counts = np.unique(predicted, return_counts=True)
print(dict(zip(unique, counts)))<jupyter_output>0.8742113422504237
{0: 112899, 1: 14757}<jupyter_text>Выводы:* подобраны наилучшие гиперпараметры для **Random Forrest** и для **Logistic Regression*** на Random Forrest не удалось получить приемлимое значение F1 даже на обучающих данных, при этом при таком большом количестве параметро обучается очень долго* Logistic Regression немного переобучается  4. Тестирование Посчитаем TF-IDF для тестовой выборки<jupyter_code>tf_idf_test = count_vect.transform(corpus_test) 
print("Размер массива TF-IDF:", tf_idf_test.shape)
predicted_test = model_rfc.predict(tf_idf_test)
print(f"F1 score Random Forrest: {f1_score(target_test, predicted_test)}")
predicted_test = model_logreg.predict(tf_idf_test)
print(f"F1 score Logistic Regression: {f1_score(target_test, predicted_test)}")<jupyter_output>F1 score Logistic Regression: 0.7809917355371901<jupyter_text>Сравним есть ли разница по сравнению с лемматизированных текстом с помощью TextBLob<jupyter_code>tf_idf_test_tblob = count_vect_tblob.transform(corpus_test_tblob) 
print("Размер массива TF-IDF:", tf_idf_test_tblob.shape)
predicted_test_tblob = model_logreg.predict(tf_idf_test_tblob)
print(f"F1 score Logistic Regression (TextBlob): {f1_score(target_test, predicted_test)}")<jupyter_output>F1 score Logistic Regression (TextBlob): 0.7809917355371901
{'fraction_non_alphanumeric': 0.11489113530326595, 'fraction_numerical': 0.026341368584758943, 'mean_word_length': 6.013633265167008, 'pattern_counts': {'":': 4, '<': 50, '<?xml version=': 0, '>': 50, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '220456', 'license': 'Unlicense', 'n_tokens_mistral': 4141, 'n_tokens_neox': 4211, 'n_words': 870, 'path': 'Toxic_comments/toxic_comments_predictions.ipynb', 'repo_name': 'brut0/yandex.praktikum_ds_projects', 'size': '10288'}