<jupyter_start><jupyter_text>Напишем класс для выбора атрибута<jupyter_code>class FeatureSelector(BaseEstimator, TransformerMixin):
    """
    Отвечает за выбор признака. В конкретном решении можно обойтись без него
    """
    
    def __init__(self, column):
        self.column = column

    def fit(self, X, y=None):
        return self

    def transform(self, X, y=None):
        return X[[self.column]]<jupyter_output><empty_output><jupyter_text>Напишем классы для очистки текста1. Стандартная очистка текста от сложноинтепретируемых значений<jupyter_code>class Cleaner(BaseEstimator, TransformerMixin):
    def __init__(self,column):
        self.column = column
        
    @staticmethod
    def clean_text(text):
        if not isinstance(text, str):
            text = str(text)
        
        text = text.lower() # делаем все буквы маленькими
        text = text.strip('\n').strip('\r').strip('\t') # удаляет начальные и конечные символы в строке
        text = re.sub("-",' ',str(text))
        text = re.sub("-\s\r\n\|-\s\r\n|\r\n", '', str(text)) # re.sub ищет шаблон в подстроке и заменяет его на строку
        text = re.sub("[0-9]|[-—.,:;_%©«»?*!@#№$^•·&()]|[+=]|[[]|[]]|[/]|", '', text)
        text = re.sub(r"\r\n\t|\n|\\s|\r\t|\\n", ' ', text)
        text = re.sub(r'[\xad]|[\s+]', ' ', text.strip())
        text = re.sub("\n", ' ', text)
        stopword_ru = stopwords.words('russian')
        tokens = list(tokenize(text))
        words = [_.text for _ in tokens]
        words = [w for w in words if w not in stopword_ru]
    
        return " ".join(words)
    
    def fit(self, X, y= None):
        return self
    
    def transform(self, X, y = None):
        X[self.column] = X[self.column].apply(lambda x: self.clean_text(x),1)
        return X[[self.column]]<jupyter_output><empty_output><jupyter_text>2. Для нормализации слов мною был сделан выбор в пользу **лемматизации**.В отличии от **стемминга**(грубая форма, попытается отбросить суффиксы и т.д.), лемматизация превращает различные формы слов в меньшее кол-во вариантов этих слов с **минимум потери смысловой нагрузки**.<jupyter_code>class Lemma(BaseEstimator, TransformerMixin):

    def __init__(self, column):
        self.column = column
        self.cache = {}
        self.morph = pymorphy2.MorphAnalyzer()
        self.stopword_ru = stopwords.words('russian')
    
    def lemmatization(self,text):

        if not isinstance(text, str):
            text = str(text)
    
        tokens = list(tokenize(text))
        words = [_.text for _ in tokens]

        words_lem = []
        for w in words:
            if w[0] == '-': 
                w = w[1:]
            if len(w)>1: 
                if w in self.cache: 
                    words_lem.append(self.cache[w])
                else: 
                    temp_cach = self.cache[w] = self.morph.parse(w)[0].normal_form
                    words_lem.append(temp_cach)
    
        words_lem_without_stopwords=[i for i in words_lem if not i in self.stopword_ru] 
    
        return ' '.join(list(words_lem_without_stopwords))
    
    def fit(self, X, y = None):
        return self
    
    def transform(self, X, y = None):
        X[self.column] = X[self.column].apply(lambda x: self.lemmatization(x),1)
        return X[self.column].values.astype('U')<jupyter_output><empty_output><jupyter_text>Создадим пайплайны для 4 классификаторов:- MultinomialNB- RandomForestClassifier- LogisticRegression- SGDClassifier<jupyter_code>from sklearn.naive_bayes import MultinomialNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.linear_model import SGDClassifier
classifier_1 = Pipeline([('selector', FeatureSelector(column='comment')),
                         ('cleaner', Cleaner(column='comment')),
                         ('lemma', Lemma(column='comment')),
                         ('tfidf', TfidfVectorizer(sublinear_tf=True,
                                                            strip_accents='unicode',
                                                            analyzer='word', # аналитика по словам
                                                            token_pattern=r'\w{1,}', # выдергиваем разделители
                                                # длина ngram(ngram - смотрим на последовательности слов длиной 1 с шагом 1)
                                                            ngram_range=(1, 1), 
                                                            max_features=10000)),
                         ('clf', MultinomialNB())])

classifier_2 = Pipeline([('selector', FeatureSelector(column='comment')),
                         ('cleaner', Cleaner(column='comment')),
                         ('lemma', Lemma(column='comment')),
                         ('tfidf', TfidfVectorizer(sublinear_tf=True,
                                                            strip_accents='unicode',
                                                            analyzer='word', 
                                                            token_pattern=r'\w{1,}', 
                                                            ngram_range=(1, 1), 
                                                            max_features=10000)), 
                         ('clf', SGDClassifier(loss='modified_huber', penalty='l2', alpha=1e-3))])


classifier_3 = Pipeline([('selector', FeatureSelector(column='comment')),
                         ('cleaner', Cleaner(column='comment')),
                         ('lemma', Lemma(column='comment')),
                         ('tfidf', TfidfVectorizer(sublinear_tf=True,
                                                            strip_accents='unicode',
                                                            analyzer='word', 
                                                            token_pattern=r'\w{1,}', 
                                                            ngram_range=(1, 1), 
                                                            max_features=10000)), 
                         ('clf', LogisticRegression(penalty='l2',C=2))])

classifier_4 = Pipeline([('selector', FeatureSelector(column='comment')),
                         ('cleaner', Cleaner(column='comment')),
                         ('lemma', Lemma(column='comment')),
                         ('tfidf', TfidfVectorizer(sublinear_tf=True,
                                                            strip_accents='unicode',
                                                            analyzer='word', 
                                                            token_pattern=r'\w{1,}', 
                                                            ngram_range=(1, 1), 
                                                            max_features=10000)), 
                         ('clf', RandomForestClassifier())])<jupyter_output><empty_output><jupyter_text>Разбиение данных и кросс-валидация<jupyter_code># Разбиение данных
X_train, X_test, y_train, y_test = train_test_split(df.drop('toxic', 1), 
                                                    df['toxic'], random_state=42)

#save test
X_test.to_csv("X_test.csv", index=None)
y_test.to_csv("y_test.csv", index=None)
#save train
X_train.to_csv("X_train.csv", index=None)
y_train.to_csv("y_train.csv", index=None)<jupyter_output><empty_output><jupyter_text>Сделаем кросс-валидацию каждого классификатора на 6 фолдах, в кач-ве скоринга используем ROC_AUC<jupyter_code>%%time
#запустим кросс-валидацию классификаторов
cv_scores_1 = cross_val_score(classifier_1, X_train, y_train, cv=6, scoring='roc_auc') 
cv_score_1 = np.mean(cv_scores_1)
print('MultinomialNB CV score is {}'.format(cv_score_1))

cv_scores_2 = cross_val_score(classifier_2, X_train, y_train, cv=6, scoring='roc_auc') 
cv_score_2 = np.mean(cv_scores_2)
print('SGDClassifier CV score is {}'.format(cv_score_2))

cv_scores_3 = cross_val_score(classifier_3, X_train, y_train, cv=6, scoring='roc_auc') 
cv_score_3 = np.mean(cv_scores_3)
print('LogisticRegression CV score is {}'.format(cv_score_3))

cv_scores_4 = cross_val_score(classifier_4, X_train, y_train, cv=6, scoring='roc_auc') 
cv_score_4 = np.mean(cv_scores_4)
print('RandomForestClassifier CV score is {}'.format(cv_score_4))<jupyter_output><ipython-input-8-7a93630c41af>:14: FutureWarning: Possible nested set at position 39
  text = re.sub("[0-9]|[-—.,:;_%©«»?*!@#№$^•·&()]|[+=]|[[]|[]]|[/]|", '', text)<jupyter_text>Обучим классификаторы и для каждого получим скор<jupyter_code>%%time

classifier_1.fit(X_train, y_train)
y_score_1 = classifier_1.predict_proba(X_test)[:, 1]

classifier_2.fit(X_train, y_train)
y_score_2 = classifier_2.predict_proba(X_test)[:, 1]

classifier_3.fit(X_train, y_train)
y_score_3 = classifier_3.predict_proba(X_test)[:, 1]

classifier_4.fit(X_train, y_train)
y_score_4 = classifier_4.predict_proba(X_test)[:, 1]
from sklearn.metrics import precision_recall_curve

precision_1, recall_1, threshold_1 = precision_recall_curve(y_test.values, y_score_1)
precision_2, recall_2, threshold_2 = precision_recall_curve(y_test.values, y_score_2)
precision_3, recall_3, threshold_3 = precision_recall_curve(y_test.values, y_score_3)
precision_4, recall_4, threshold_4 = precision_recall_curve(y_test.values, y_score_4)


#Найдем F-меру для каждого классификатора
f_score_1 = (2*precision_1 * recall_1)/(precision_1 + recall_1)
f_score_2 = (2*precision_2 * recall_2)/(precision_2 + recall_2)
f_score_3 = (2*precision_3 * recall_3)/(precision_3 + recall_3)
f_score_4 = (2*precision_4 * recall_4)/(precision_4 + recall_4)

# найдем индекс максимального значения F-меры. При этом эти же индексы будут принадлежать оптимальным значениям 
# точности, полноты и порога
ix1 = np.argmax(f_score_1)
ix2 = np.argmax(f_score_2)
ix3 = np.argmax(f_score_3)
ix4 = np.argmax(f_score_4)

roc_score_1 = roc_auc_score(y_test, y_score_1)
roc_score_2 = roc_auc_score(y_test, y_score_2)
roc_score_3 = roc_auc_score(y_test, y_score_3)
roc_score_4 = roc_auc_score(y_test, y_score_4)
from sklearn.metrics import precision_recall_curve, roc_curve, roc_auc_score, confusion_matrix

precision, recall, thresholds = precision_recall_curve(y_test.values, y_score_1)
fscore = (2 * precision_1 * recall_1) / (precision_1 + recall_1)
ix = np.argmax(fscore)
print('Best Threshold=%f, F-Score=%.3f, Precision=%.3f, Recall=%.3f' % (thresholds[ix1], 
                                                                        fscore[ix1],
                                                                        precision[ix1],
                                                                        recall[ix1]))
print(roc_auc_score(y_test.values,y_score_1))
pd.DataFrame(data = [[f_score_1[ix1],roc_score_1,precision_1[ix1],recall_1[ix1],threshold_1[ix1]],
                    [f_score_2[ix2],roc_score_2, precision_2[ix2],recall_2[ix2],threshold_2[ix2]],
                    [f_score_3[ix3],roc_score_3,precision_3[ix3],recall_3[ix3],threshold_3[ix3]],
                    [f_score_4[ix4],roc_score_4,precision_4[ix4],recall_4[ix4],threshold_4[ix4]]], 
            index = ['MultinomialNB','SGDClassifier','LogReg','RFC'],
            columns = ['F_score','ROC-AUC','Precision','Recall','Threshold'])<jupyter_output><empty_output><jupyter_text>**Остановимся на наивном Байесовском классификаторе. Модель проста и в нашем случае оказалась довольно полезна**Посмотрим на AUC_ROC<jupyter_code>import seaborn as sns
import itertools
import matplotlib.pyplot as plt


%matplotlib inline
sns.set(font_scale=1.5)
sns.set_color_codes("muted")


plt.figure(figsize=(10, 8))
fpr, tpr, thresholds_ = roc_curve(y_test, y_score_1, pos_label=1)
lw = 2
plt.plot(fpr, tpr, lw=lw, label='ROC curve ')
plt.plot([0, 1], [0, 1])
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve')
plt.savefig("ROC.png")
plt.show()
def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    """
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting `normalize=True`.
    """
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
        print("Normalized confusion matrix")
    else:
        print('Confusion matrix, without normalization')

    print(cm)

    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, cm[i, j],
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
font = {'size' : 15}

plt.rc('font', **font)

cnf_matrix = confusion_matrix(y_test, y_score_1>thresholds[ix1])
plt.figure(figsize=(8, 6))
plot_confusion_matrix(cnf_matrix, classes=['Nontoxic', 'toxic'], normalize=True,
                      title='Confusion matrix')
plt.show()
TN = cnf_matrix[0][0]
FN = cnf_matrix[1][0]
TP = cnf_matrix[1][1]
FP = cnf_matrix[0][1]
TPR = TP/(TP+FN)
FPR = FP/(FP+TN)
TNR = TN/(FP+TN)
TPR, FPR, TNR<jupyter_output><empty_output><jupyter_text>- Мы может автоматически отсеивать ≈ 83% токсичных комментариев, что значительно облегчает жизнь тем же модераторам- Всего 7,5% не токсичных комментариев получают метку 'токсичный' **Как и говорил ранее, создадим полный пайплайн для лучшей модели и обучим ее на всех данных**<jupyter_code>classifier = Pipeline([('selector', FeatureSelector(column='comment')),
                         ('cleaner', Cleaner(column='comment')),
                         ('lemma', Lemma(column='comment')),
                         ('tfidf', TfidfVectorizer(sublinear_tf=True,
                                                            strip_accents='unicode',
                                                            analyzer='word', # аналитика по словам
                                                            token_pattern=r'\w{1,}', # выдергиваем разделители
                                                # длина ngram(ngram - смотрим на последовательности слов длиной 1 с шагом 1)
                                                            ngram_range=(1, 1), 
                                                            max_features=10000)),
                         ('clf', MultinomialNB())])
X_train
%%time
classifier.fit(df[['comment']],df[['toxic']])
with open("NB_pipeline.dill", "wb") as f:
    dill.dump(classifier, f)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12461164392813724, 'fraction_numerical': 0.018573551263001486, 'mean_word_length': 2.2593000220118866, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 65, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '323436', 'license': 'MIT', 'n_tokens_mistral': 5002, 'n_tokens_neox': 4948, 'n_words': 872, 'path': 'Analyzing_tone_of_comments/selecting_model.ipynb', 'repo_name': 'mahhets/projects', 'size': '14806'}