NTA

Наивный Байесовский классификатор для фильтрации спама

Использование наивного Байесовского классификатора для фильтрации спама — довольно старый и известный способ фильтрации сообщений. В его основе лежит простая идея – на обучающих данных для каждого слова в сообщении вычисляется оценка вероятности того, что письмо с этим словом является спамом и, исходя их этого, для вновь пришедшего письма на основании всех слов, вычисляется его вероятность оказаться спамом. Это модель предполагает серьезное допущение, что вероятность встретить одно слово не зависит от вероятности встретить другое слово в спамном или неспамном сообщении (отсюда и наивность в названии), однако до сих пор такой классификатор может использоваться и показывать хорошие результаты, даже по сравнению с более продвинутыми методами.

Рассмотрим подробнее как данный алгоритм можно пошагово реализовать на python. Для этого возьмем данные с сайта Kaggle (SMS Spam Collection Dataset). В датасете содержится информация о 5572 сообщений, среди которых 13% спама.

Разделим данные на обучающую и тестовую выборки. Сохраним обучающий датасет в переменную df. Для начала напишем простую вспомогательную функцию для того, что бы получить множество всех слов:

import re def tokenize(message): message = message.lower() all_words = re.findall("[a-z0-9]+", message) return set(all_words)

Теперь перейдем непосредственно к алгоритму. Для начала для каждого слова нужно найти оценку вероятности встретить его в спамном или неспамном сообщении. Для этого просто посчитаем сколько раз каждое слово встречается в каждом из типов сообщений. Также напишем функцию под это:

from collections import defaultdict import pandas as pd def count_words(data): counts = defaultdict(lambda: [0,0]) for index, row in data.iterrows(): for word in tokenize(row['text']): counts[word][0 if row['spam'] else 1] +=1 return counts

Воспользуемся этой функцией и сохраним результат в переменную freq:

freq = count_words(df)

В результате получаем словарь, следующего вида:

Мы можем видеть каждое слово и сколько раз оно встречается в каждом типе сообщений. Теперь переходим к вычислению веса, то есть нам необходимо поделить конкретную частоту на общее количество сообщений каждого типа. Но здесь есть еще одно допущение. Если какое-то слово присутствует только в сообщениях без спама, то сообщение с этим словом классификатор всегда будет относить к неспамному. Что бы этого избежать при расчете вероятности добавим коэффициент сглаживания равный единицы. Это позволит избежать нулевые вероятности.

Итак идем дальше. Сохраним в переменных общее число сообщений со спамом и без него:

all_spam = df['spam'].loc[df['spam'] == 1].count() all_not_spam = df['spam'].loc[df['spam'] == 0].count()

Напишем очередную функцию. Она будет возвращать для каждого слова уже посчитанную вероятность:

def prob(freq, all_spam, all_not_spam, k = 1): return [ (word, (frequency[0] + k) / (all_spam + 2*k), (frequency[1] + k) / (all_not_spam + 2*k)) for word, frequency in counts.items()]

Воспользуемся функцией и сохраним результат в переменной result:

result = prob(freq, all_spam, all_not_spam)

Получим данные следующего вида:

Теперь мы можем посчитать вероятности для сообщений. Также сделаем это с помощью функции. По идее мы должны перемножить частные вероятностные друг на друга. Но так как вероятности могут представлять собой очень маленькие числа, их перемножение может привести к исчезновению разрядов. Поэтому воспользуемся алгебраическими свойствами и заменим действия на сумму логарифмов:

import math def final(word_probs, message): # Также обрабатываются сообщения message_words = tokenize(message) # изначально вероятности спама и неспама равны нулю spam_prob = not_spam_prob = 0.0 # наращиваем вероятности в циклах for word, prob_if_spam, prob_if_not_spam in word_probs: # если слово в сообщении используем посчитанную вероятность if word in message_words: spam_prob += math.log(prob_if_spam) not_spam_prob += math.log(prob_if_not_spam) # если слова нет в сообщении используем вероятность противоположного события, т.е 1 - посчитанная вероятность else: spam_prob += math.log(1.0 - prob_if_spam) not_spam_prob += math.log(1.0 - prob_if_not_spam) e_spam_prob = math.exp(spam_prob) e_not_spam_prob = math.exp(not_spam_prob) # непосредственно формула вероятность по теореме Баейса return e_spam_prob/(e_spam_prob+e_not_spam_prob)

Из тестового набора данных собираем датасет с вероятностями:

for index, row in test.iterrows(): df_test = df_test.append({'text': row['text'],'prob': final(result, row['text']), 'spam_': row['spam']}, ignore_index=True)

Получаем датасет вида:

Осталось только поставить порог отнесения к классу на вероятность и проверить работу алгоритма на тестовых данных.Так как в результате работы предыдущих функций мы получили значение вероятности, от него необходимо перейти непосредственно к метке. Данная функция возвращает метку спама исходя из полученной вероятности:

def func(row): if row['prob']>0.5: return 1 else: return 0 df_test['spam_final'] = df_test.apply(func, axis = 1) accuracy_score(df['spam'], df['spam_final'])

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

Посмотрим как это будет работать при изменение порога. Для начала попробуем установить порог ровно посередине вероятности – 50%. Для порога в 50% точность составила 76,88%. Попробуем немного сдвинуть. Повторим те же действия для порога в 60% и получим точность уже 98,42%. Получаем очень хороший результат для такой простой модели.

0
3 комментария
Популярные
По порядку
Eleonora Poslovskaya

Хорошая статья о применении байесовского классификатора, но есть пара замечаний. Несколько спорным выглядит подсчёт вероятностей встречаемости слов в спам и не-спам сообщениях сразу на всем датасете. Таким образом даже используя сглаживание вы допускаете утечку таргета для сообщений из тестового множества в случае редких слов, что в дальнейшем скажется при подсчёте качества модели.
Есть ошибка в функции prob — вместо counts видимо хотели использовать её первый аргумент.
И, пожалуйста, используйте apply или loc вместо iterrows - он производит обертку каждой строки в отдельный Series и за счёт этого медленнее apply минимум в 2 раза.
Оценивать модель через точность хорошая идея, но в этой задаче нам несколько мешает дисбаланс классов - даже если бы модель всегда выдавала 0, мы бы получили 87% точности. Можно считать ROC AUC, который от этих проблем избавлен. Также легко гуглится определение оптимального порога классификации при помощи ROC кривой, если не хочется подбирать наугад.

Ответить
1
Развернуть ветку
NTA

Спасибо за комментарий! Действительно, полезные советы. Данная статья написана, чтобы скорее отразить общую идею применения такого метода, ее можно продолжать совершенствовать.

Ответить
0
Развернуть ветку
Quantum Artist

А если проверить - есть ли у письма марка? Если есть - то это дорогой спам, а если нет - то точно спам.
Письма с адресов апрувленных владельцем почтового ящика вряд ли будут спамом. Остальные - за борт.
Еще можно присылать один цифровой доллар адресату с тем чтобы если все ок, в ответ этот доллар присылался назад.
А если не ок - сами понимаете.

Ответить
0
Развернуть ветку
Читать все 3 комментария
Анастасии звонят чаще, чем Владимиры — Yota подготовила статистику клиентов по именам

По данным Yota за 2021 год, самые часто встречающиеся мужские имена среди пользователей мобильного оператора – Александр, Сергей, Алексей. Женские – Елена, Наталья, Татьяна. При этом Александров на 21,4% больше, чем Елен.

Python-разработчик, UX-писатель и Product-менеджер. На кого учатся россияне и сколько тратят на онлайн-образование

Ко Дню студента мы выяснили, как в 2021 году изменился спрос на онлайн-образование по сравнению с 2020 годом. Рассказываем, что изменилось за год, и какие направления онлайн-образования пользуются наибольшей популярностью.

Mastercard снизит межбанковские комиссии при оплате картой на Wildberries, Aliexpress, Ozon и других маркетплейсах Статьи редакции

Условия начнут действовать с 31 января 2022 года.

Леонид Агутин стал лицом новой кампании Авито по цифровой безопасности

В новой digital-кампании Авито, направленной на обучение цифровой безопасности, Леонид Агутин рассказывает, как заботиться о близких в онлайн-среде, и о главных правилах поведения в сети. Ролик с участием Леонида Агутина появился на YouTube и в социальных сетях, также он будет распространяться в популярных сообществах в мессенджерах.

39 ребрендингов 2021. Как поменялись компании из IT, финтеха, телекома, ритейла, медиа
Такси и каршеринг получили новую фиолетово-зеленую оклейку
Прибыль Microsoft в прошлом квартале выросла благодаря спросу на облачные сервисы

Корпорация Майкрософтзаявила, что ее прибыль продолжала расти в прошлом квартале, поскольку ее бизнес облачных услуг оставался сильным. Продажи за квартал, закончившийся в декабре, достигли 51,7 млрд долларов, что на 20% больше, чем годом ранее.Чистая прибыль выросла на 21% до 18,8 млрд долларов, пишет WSJ.

Как работает реферальная программа в Playgendary

30-40% принятых офферов — это рекомендации, которых мы получаем по 200-300 ежемесячно. Рассказываем, как пришли к такому результату.

Тинькофф вгоняет клиентов в убытки

Итак, вот и еще одна, очередная история о том, как Тинькофф инвестиции заставляет клиентов платить за собственные технические ошибки и вгоняет их в убытки. А теперь по порядку:

Патенты и электромобили: что охраняют «Яндекс», автогиганты и стартапы

Технологии совершенствования аккумуляторов и системы управления стали самыми патентуемыми разработками, связанными с электромобилями. Такие данные представил в своем исследовании крупнейший агрегатор американских и общемировых патентных данных IFI Claims.

Как попасть в ЧС телефонных спамеров?

Реальная рабочая схема попадания в черный список телефонных спамеров. Сработала против спама от Совкомбанка, Альфа-банка, Дом.ру, Билайна, ремонта оконных рам, расчетных счетов для бизнеса и не только.

null