Рубрики
Без рубрики

Введение в машинное обучение и НЛП с Python и Weka

В этом учебном пособии кратко рассказывается о машинном обучении с использованием Python(2.x) и weka. Задача состоит в том, чтобы создать простой спам-фильтр для электронных писем и изучить концепции машинного обучения.

Автор оригинала: Benjamin Cohen.

В этом уроке вы кратко познакомитесь с машинным обучением с помощью Python (2.x) и Weka , инструмента обработки данных и машинного обучения. Задача состоит в том, чтобы создать простой спам-фильтр для электронных писем и изучить концепции машинного обучения.

Эта статья написана командой Codementor и основана на Часы работы Codementor по Кодементору Бенджамин Коэн , специалист по обработке данных, специализирующийся на обработке естественного языка.

Что такое машинное обучение?

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

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

Тем не менее, то, что мы действительно собираемся разработать здесь, – это система автоматического разграничения между двумя или более классами. В случае этого упражнения спам или не спам . Мы хотим сделать все это без необходимости вручную сообщать компьютеру наши правила. Одна из вещей, необходимых для этого и любой проблемы машинного обучения, – это набор данных . Общий процесс заключается в том, что машина изучает правила из нашего набора данных (который, как мы надеемся, представляет собой то, как выглядят все наборы данных), а затем использует то, что она узнала, на новых данных. Другими словами, без набора данных в качестве основы, чтобы сообщить машине, что правильно и как выглядят наши данные, мы не можем разработать правила, по которым машина будет учиться. Нам нужны материалы для машины, чтобы учиться на них.

Кстати, мы перейдем к этому упражнению, уже подготовленному с помощью набора данных. Однако, когда вы подходите к проблеме в целом, у вас часто еще нет данных, и вы не знаете, какие данные вам нужны.

Если бы вам нужно было написать систему для идентификации спам-писем, набор данных, который мы используем в этом упражнении,-это именно те данные, которые вам нужны, но в реальных приложениях вам может потребоваться найти другой набор данных для перекрестной ссылки. Адреса электронной почты довольно шаткие, так как спам-адреса часто закрываются, поэтому они создают много новых адресов электронной почты. Одна вещь, которую вы потенциально можете сделать, – это оценить вероятность того, что доменное имя отправит спам. Например, не многие люди используют gmail для спама, потому что Google отлично справляется с обнаружением и закрытием учетных записей электронной почты со спамом. Тем не менее, некоторые домены, такие как hotmail, могут иметь шаблон, в котором, если отправитель использует hotmail, это, скорее всего, будет спамом, поэтому вы определенно можете узнать, можно ли извлечь уроки из изучения адресов электронной почты. Еще одна классическая функция, на которую мы могли бы обратить внимание, – это метаинформация, например, время отправки электронного письма. Все данные, которые мы рассмотрим в нашей деятельности, – это то, что на самом деле было в письме, то есть текст.

Упражнение

Вот ссылка на проект GitHub вы можете раскошелиться и использовать вместе с этим действием, и убедитесь, что вы установили Weka на своей машине – это бесплатное программное обеспечение.

Набор данных, который мы будем использовать здесь, содержит примерно 1300 электронных писем. Вы можете быстро просмотреть две папки, которые у нас есть, is-spam и not-spam , чтобы узнать, какие письма у нас есть.

Например:

Find Peace, Harmony, Tranquility, And Happiness Right Now!

это довольно типичное спам-письмо, в то время как

Re: EntrepreneursManoj Kasichainula wrote;
>http://www.snopes.com/quotes/bush.htm
>
>Claim:   President George W. Bush proclaimed, "The problem with
>the French is that they don't have a word for entrepreneur."
>
>Status:   False.



>Lloyd Grove of The Washington Post was unable to reach Baroness
>Williams to gain her confirmation of the tale, but he did
>receive a call from Alastair Campbell, Blair's director of
>communications and strategy. "I can tell you that the prime
>minister never heard George Bush say that, and he certainly
>never told Shirley Williams that President Bush did say it,"
>Campbell told The Post. "If she put this in a speech, it must
>have been a joke."

So some guy failed to reach the source, but instead got spin doctor to
deny it.  Wot, is he thick enough to expect official confirmation
that, yes, Blair is going around casting aspersions on Bush???

It's an amusing anecdote, I don't know if it's true or not, but certainly
nothing here supports the authoritative sounding conclusion "Status: False".


R
http://xent.com/mailman/listinfo/fork

это обычная электронная почта.

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

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

Как Происходит Процесс Естественного Языка?

Если вы заметили, письма как от is-spam , так и от not-spam являются текстовыми. Текст и язык-это вещи, которые мы, люди, понимаем очень хорошо, но компьютеры понимают менее хорошо. В нашем случае мы должны преобразовать этот английский язык в то, что могут понять компьютеры, – что в общем случае было бы числами. Именно здесь вступает в действие Процесс естественного языка (НЛП), когда мы пытаемся заставить компьютер понять суть контекста этих сообщений. Часть машинного обучения заключается в понимании закономерностей в числах и применении этих закономерностей к будущим вещам.

Что такое функция?

Функция – это в основном своего рода преобразование наших входных данных в – в нашем случае-числовую функцию или просто число, из которого наша машина может учиться. Мы хотим получить набор номеров или функцию, как мы ее называем, которая представляет собой электронное письмо, подобное этому:

The DEBT-$AVER Program              SKYXTGM



Copyright 2002 - All rights reserved

If you would no longer like us= to contact you or feel that you have
received this email in error, please click here= to unsubscribe.

Чем лучше мы сможем представить это электронное письмо в числовой форме, тем больше шаблонов мы, надеюсь, увидим в наших электронных письмах, если выберем хорошие функции.

Прежде чем писать какие-либо другие функции, мы просто посмотрим на количество слов. Вот код, чтобы сделать это в нашем features.py :

def numwords(emailtext):
  splittext = emailtext.split(" ")
  return len(splittext)

Однако, чтобы позволить Weka анализировать эти данные, нам нужно превратить их в файл .arff .

Есть сценарий, который я назвал feature_extract.py . Прямо сейчас вам действительно не нужно беспокоиться о том, что это такое, но вот код, если вам интересно:

import  os, re
import math, glob
import features
import inspect 

def main():
  arff = open("spam.arff", "w")
  
  ben_functions = inspect.getmembers(features, inspect.isfunction)
  feature_funcitons = []
  feature_funcitons +=  list([f[1] for f in ben_functions])
  RELATION_NAME = "spam"									 
  arff.write("@RELATION " + RELATION_NAME + "\n")
  for feature in feature_funcitons:
    arff.write("@ATTRIBUTE " +\
    str(feature.__name__) + " REAL\n")  #change this if we
                    #have non-real number
                    #values
    
    ###PREDEFINED USER FEATURES#######
  arff.write("@ATTRIBUTE SPAM {True, False}\n") 
  
  arff.write("@DATA\n")
  
  spam_directory = "is-spam"
  not_spam = "not-spam"
  os.chdir(spam_directory)
  for email in glob.glob("*"):#ITERATE THROUGH ALL DATA HERE
    extract_features(open(email).read(), feature_funcitons, arff, True)
   
  os.chdir('../'+not_spam)
  for email in glob.glob("*"):#ITERATE THROUGH ALL DATA HERE 
    extract_features(open(email).read(), feature_funcitons, arff, False)

def numwords(emailtext):
  splittext = emailtext.split(" ")
  return len(splittext)

def extract_features(data, feature_funcitons, arff, spam):
  values = []
  buff = ""

  for feature in feature_funcitons:
    value = feature(data)
  values.append(value)
  if spam:
  buff += (",".join([str(x) for x in values]) + ', True' + "\n")
  else:
  buff += (",".join([str(x) for x in values]) + ', False' + "\n")

  arff.write(buff)
  
if __name__ == "__main__":
  main()

В основном этот скрипт применяет все функции, которые мы написали в features.py и помещает его в свой файл .arff .

Итак, после запуска feature_extract.py , давайте взглянем на наш файл spam.arff :

@RELATION spam
@ATTRIBUTE numwords REAL
@ATTRIBUTE SPAM {True, False}
@DATA
1003,True
638, True
74, True
88, True
…

@Relation – это просто название нашей проблемы, и каждый из атрибутов @ | является функцией. Первая особенность, которую я здесь имею, - это просто количество слов, и REAL означает, что это реальное число. Наш последний атрибут-это разные классы, и в этом случае SPAM может быть либо True , либо False . После этого у нас просто есть наши данные, которые содержат значение каждого атрибута. Например, строка 1003, True означает, что письмо содержит 1003 слова и является спамом.

Этот файл .arff – это не то, на что нам никогда не придется смотреть, так как файл предназначен только для нас.

Запустите Weka и загрузите файл spam.arff .

Вы увидите, что на вкладке process левый столбец содержит все наши функции. Если вы нажмете “СПАМ”, мы увидим распространение спама или нет в правой колонке. Вы можете видеть, что из наших 1388 писем 501 являются спамом, а 887-нет.

Давайте посмотрим на numwords с Weka:

предварительный процесс weka

Вы увидите, что у нас есть распределение, которое сейчас не говорит нам о многом, и у нас также есть некоторая статистика. Например, наименьшее количество слов 1 , и максимум 13894 , что очень высоко. По мере того, как мы получаем больше функций, мы можем видеть больше различий в распределениях и определять, какие из них хороши, а какие нет.

Как выбирать, создавать и использовать функции

Следующая важная вещь, которую мы имеем в Weka, – это вкладка Classify .

классификатор weka

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

коробка классификатора weka

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

В наших тестовых опциях мы будем использовать Процентное разделение . Разбиение на 80% приведет к обучению модели на 80% наших данных. В нашем случае это примерно 1000 из 1300 писем. Оставшиеся 20% будут использованы для тестирования модели, и мы попытаемся посмотреть, какой процент из них мы получим правильно.

процентное разделение weka

Мы хотим использовать процентное разделение, потому что не хотим столкнуться с проблемой, называемой “чрезмерной подгонкой”, когда мы изучаем тенденции в наших обучающих данных, которые могут не существовать в реальном мире. Если мы изучаем очень специфический набор, который случайно встречается в наших обучающих данных, это на самом деле ничего не говорит нам о шаблоне, поскольку шаблон может не наблюдаться в других данных. Важно иметь в виду, что мы всегда хотим тестировать данные, которые мы раньше не видели, чтобы мы могли имитировать наличие реальных данных для тестирования нашего шаблона, а не тестировать те же данные, с которыми мы тренируемся.

В конце концов мы рассмотрим пример переоснащения, но давайте сначала посмотрим, как работает наша функция numwords , выбрав правило под названием OneR и запустив тест:

века онер

Самое большое число, на которое мы обратим внимание, – это “правильно классифицированные экземпляры”:

Correctly Classified Instances	  186		66.9065 %
Incorrectly Classified Instances	  92		33.0935 %

Эти цифры отражают нашу точность обнаружения спама. Вы можете видеть, что, используя имеющиеся у нас функции, мы правильно классифицировали 66,9% наших писем как спам или не-спам . При первом осмотре это выглядит большим прогрессом.

Однако точность 66,9% не так уж велика, потому что, если мы посмотрим на наше исходное распределение данных, у нас есть 1388 писем, и из них 887 не являются спамом, или примерно 64% не являются спамом, так что точность 67% не так уж велика.

Концепция , которая была проиллюстрирована, называется базовой линией , которая является супер простой классификацией, которую мы используем для сравнения всех других наших результатов. Если бы наше распределение было 50:50 спама, а не спама, и мы получили точность 66,9%, это было бы действительно здорово, так как мы улучшились примерно на 17%. Здесь мы улучшились только на 3%.

Что такое Матрица путаницы?

Еще одна вещь, на которую следует обратить внимание, – это нижняя часть Weka в нашей матрице путаницы :

=== Confusion Matrix ===
  a      b      <— classified as
  43    52   | a = True
  40   147   | b = False

Строка “a b” представляет прогнозируемый класс, в то время как столбец “a b” представляет фактический класс. Здесь a – это когда, в то время как b есть.

Итак, из a мы классифицировали 43 правильно, в то время как мы неправильно классифицировали 52 как b , и в этом случае спам или нет-спам. Нижний ряд показывает наше замешательство или то, что мы ошиблись.

Эта матрица путаницы полезна, так как если бы у нас была 0 в левом нижнем углу мы будем знать, что мы классифицировали все в is-spam справа, но это также будет означать, что мы классифицировали слишком агрессивно и должны немного облегчить наши правила, изменив наши параметры.

Повышение Точности Классификации Weka

Чтобы повысить нашу точность, давайте вернемся к нашему features.py и напишите какой-нибудь код, чтобы получить больше возможностей.

В настоящее время у нас есть функция, которая получает весь текст письма и возвращает несколько слов. Опять же, это будет автоматически включено в наш файл spam.arff через наш features_extract.py сценарий. Теперь мы добавим функцию, которая проверяет, находится ли письмо в формате HTML или это просто обычный текст.

Я решил добавить эту функцию, потому что знаю, что она даст нам интересный дистрибутив. Как правило, вы можете придумать, какие функции добавить, вручную просматривая свой набор данных в поисках шаблонов. Просматривая несколько писем в папках не-спам , вы можете увидеть, что некоторые из обычных писем отформатированы в формате HTML. В нашей папке is-spam некоторые из наших писем, похоже, находятся в HTML, так что это может быть хорошей функцией.

Поскольку это занятие займет меньше часа, мы будем писать только приближение, а не идеальный способ проверить, есть ли электронное письмо в HTML или нет.

def has_html(emailtext):
  return 1 if "html" in emailtext.lower() else 0

Это предполагает, что электронное письмо является HTML, если появляется слово html , что опять же может быть не так, но это упрощенно.

После того, как мы запустим наш features_extract.py скрипт в вашей консоли с

python feature_extract.py

Мы повторно откроем ваш файл .arff с помощью Weka и увидим, что в нем будет наша функция hands_html :

предварительный процесс weka

Среднее и стандартное отклонение здесь не слишком много значат. Взглянув на наш график, основанный на функции SPAM , красный цвет представляет электронные письма, которые не являются спамом. Таким образом, мы можем видеть, что, основываясь на нашей функции hands_html , большинство not-spam не имеют HTML, а большинство наших is-spam писем имеют HTML. Это именно то, что мы ожидали, и это показывает нам – по крайней мере, из визуального осмотра – что мы можем чему-то научиться из этой функции, так что это хорошая функция.

Давайте продолжим и напишем третью функцию в features.py . Основываясь на наборе данных, я заметил, что в спаме, похоже, больше ссылок, поскольку они хотят, чтобы вы что-то купили, поэтому я хочу вернуть количество ссылок.

def num_links(emailtext):
  return emailtext.count('http')

Опять же, это всего лишь приближение, и мы не ищем точное количество ссылок, так как это отнимает больше времени. Функция выше в основном предполагает , что всякий раз, когда в письме упоминается http , оно соответствует ссылке.

После запуска нашего features_extract.py опять же, мы можем снова открыть ваш файл spam.arff на Weka:

предварительный процесс weka

Как и ожидалось, мы видим, что наш минимум 0 поскольку есть электронные письма без ссылок, и одно электронное письмо на самом деле было 68 ссылки в нем.

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

Давайте взглянем на нашу вкладку classify в Weka и посмотрим, что эти функции делают для нашей точности классификации, повторно запустив наш тест.

века онер

Похоже, ничего не изменилось, хотя у нас есть новые правила, которые предположительно намного лучше, и причина этого в том, что мы использовали неправильный классификатор. Классификатор OneR рассматривает только одну функцию и разрабатывает на ее основе правила, поэтому, даже если мы повторно запустим тест, он будет смотреть только на функцию numwords и создавать на ее основе множество правил. Тем не менее, сейчас у нас есть 3 функции, и мы не уверены, что классификация по количеству слов хороша, но мы знаем, что has_html и has_link хороши, и мы хотим включить их все. Давайте переключим наш классификатор на дерево решений, J48 .

Что такое Дерево решений?

Дерево решений-это простая концепция, очень похожая на то, что мы делаем как люди, поскольку она в основном начинается с выделения решений и следования им по пути. Я собираюсь привести классический пример из http://jmvidal.cse.sc.edu/talks/decisiontrees/allslides.html : Предположим, мы хотим пойти поиграть в теннис. Мы смотрим на небо, чтобы увидеть, солнечно ли, пасмурно или идет дождь. Если пасмурно, мы играем в теннис. Если идет дождь, мы смотрим на ветер. Если ветер сильный, мы не будем играть, а если слабый, мы будем играть. Если будет солнечно, мы посмотрим на влажность, где мы играем, если влажность нормальная, и мы не будем играть, если она высокая.

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

Во всяком случае, теперь, когда мы решили переключить наш классификатор на J48, мы видим наше дерево:

weka j48

Теперь мы можем точно видеть, как принимаются решения на основе дерева, где, если num_link > 3 мы говорим True . Если это меньше 3, мы посмотрим , содержит ли письмо слово html , которое разбивает дерево на две ветви. Затем мы продолжаем опускать такие вещи, как num_link и numwords , пока не примем решение.

Как вы также можете видеть на скриншоте выше, наши правильно классифицированные экземпляры улучшились до 76,6%

Как решить, какое правило важнее другого

Я собираюсь дать упрощенный ответ, потому что реальный ответ будет очень сложным и длинным. В принципе, существует понятие чего-то, называемого “информацией”, или знанием. Мы можем спросить себя: “Сколько информации я получаю, рассматривая эту функцию?”

Например, если у нас есть две функции: солнечная и ветреная. Мы играем в теннис в 4 солнечных дня и 4 пасмурных дня, и не играем в теннис в 2 солнечных дня и 2 пасмурных дня. Эта информация на самом деле не помогает нам – мы ничего не получаем, глядя на то, солнечно или нет, так как это даже в обоих направлениях.

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

Глядя на нашу модель, вы заметите, что сначала мы смотрим на количество ссылок, а это означает, что количество ссылок-это функция с наибольшим количеством информации. Мы действительно можем посмотреть на необработанные цифры для получения информации через Weka.

Перейдем на вкладку Выбор атрибутов , выберем InfoGainAttributeEval и нажмите кнопку Пуск. Мы бы это поняли:

мы выбираем атрибуты

Нам не нужно заботиться о том, что означают все числа, но мы можем видеть, что num_link имеет наибольшую полученную информацию, за которой следует has_html , а затем numwords . Другими словами, num_link – это наша лучшая функция.

Чтобы проиллюстрировать это далее, мы можем добавить фиктивную функцию в наш features.py :

def dummy(emailtext): 
  return 1

Эта функция вернет 1 независимо от того, что это за электронное письмо. Если мы побежим feature_extract.py и перезагрузите свой spam.arff в Weka, мы должны убедиться, что не было никакой информации, полученной от просмотра нашей фиктивной функции:

мы выбираем атрибуты

Это потому, что это одно и то же для всего.

Подводя итог, мы в основном смотрим на функции, которые дают нам наибольший прирост знаний.

Чрезмерная подгонка

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

Один из пользователей Codementor в рабочее время предложил конкретные слова, которые исторически являются спамом в качестве функции, такие как “бесплатно”, “купить”, “присоединиться”, “начать”, “нажать”, “скидка”. Давайте добавим эту функцию в наш features.py и разбейте текст пробелами, чтобы мы получили все наши слова.

def spammy_words(emailtext):
  spam_words = ['join', 'free', 'buy', 'start', 'click', 'discount']
  splittext = emailtext.split(" ")
  total = 0
  for word in spam_words:
    total += splittext.count(word) #appends the count of each of these words to total

  return total

Давайте дадим этой функции шанс и перезагрузим ваш spam.arff в Weka.

предварительный процесс weka

Мы можем видеть распределение, в котором большинство наших писем, которые не являются спамом содержат ноль слов из массива spam_words , и по мере увеличения количества баров становятся более синими, что означает, что если в письме больше spam_words , то, скорее всего, оно будет спамом.

Давайте также взглянем на информационный выигрыш: мы видим, что есть некоторый информационный выигрыш, но не очень большой.

мы выбираем атрибуты

Что касается нашего классификатора, мы видим, что на самом деле у нас было небольшое снижение точности:

weka снизила точность классификации

Не говоря уже о том, что spammy_words – плохая функция, поскольку, на мой взгляд, она довольно хороша, давайте спросим себя, какова возможная причина снижения точности нашей классификации.

Лично я думаю, что точность упала из-за чрезмерной подгонки. Если вы посмотрите на дерево в нашей Weka, вы увидите, что мы создали много правил – я оцениваю по крайней мере 50 правил, и многие из них действительно специфичны.

переоснащение

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

Раньше мы думали о словах, которые, по нашему мнению, указывают на спам. Однако, как люди, мы предвзяты, основываясь на нашем опыте, но слова, о которых мы думали, могут отсутствовать в нашем наборе данных. Или это может означать, что использование слова “бесплатно”, например, так же регулярно в обычной электронной почте, просто менее распространено.

Мы хотим найти способ фильтровать spammy_words , но автоматически генерировать список слов вместо того, чтобы разбираться в этом самостоятельно.

Для этого мы должны взглянуть на распределение слов. Сначала мы должны установить словари для спам-слов и не спам-слов , и мы собираемся заполнить их следующим образом:

import os, glob
spamwords = {}
notspamwords = {}

spam_directory = "is-spam"
not_spam = "not-spam"
os.chdir(spam_directory)
for email in glob.glob("*"):#ITERATE THROUGH ALL DATA HERE 
  text = open(email).read().lower().split(' ') #gets all the text of our emails and normalizes them to lower case
  for word in text:
    if word in spamwords:
      spamwords[word] += 1
    else:
      spamwords[word] = 1

print spamwords

Давайте сохраним приведенный выше код в виде файла с именем wordcounts.py и запустите его. Мы увидим, что это большой беспорядок, содержащий огромный словарь всех наших слов и то, как часто мы их видели. В нем есть ссылки и html, но мы проигнорируем это и в основном распечатаем слова, которые мы видим чаще всего, изменив print spamwords на

keys = sorted(spamwords.keys(), key=lambda x: spamwords[x], reversed=True`)

for word in keys:
  print word, spamwords[word]

Где мы будем сортировать ключи и упорядочивать их по тому, как часто их видят.

И мы увидим, что наши наиболее часто встречающиеся слова очень распространены (например, for, a, и, you, of, to, the). Поначалу это кажется довольно плохим, так как они нам не помогают. Мы не можем искать слово “а”, потому что оно будет повсюду как в спаме, так и в спам-письмах.

Итак, чтобы действительно выяснить, какие слова обычно используются в спам-письмах, но обычно не используются в не-спам-письмах, давайте взглянем на часто используемые слова в не-спам-письмах с тем же кодом, за исключением того, что мы изменим os.chdir(spam_directory) на os.chdir('../'+not_spam) .

Мы заметим, что, как ни странно, Helvetica-это слово, которое обычно встречается в спам-письмах, но не в спам-письмах. New , money , |/e-mail , receive и business также встречаются в спам-письмах, но в меньшей степени в спам-письмах, поэтому давайте внесем изменения в нашу функцию spammy_words :

def spammy_words(emailtext):
  spam_words = ['helvetica', 'new', 'money', 'e-mail', 'receive', 'business']
  splittext = emailtext.split(" ")
  total = 0
  for word in spam_words:
    total += splittext.count(word) #appends the count of each of these words to total

  return total

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

def not_spammy_words(emailtext):
  spam_words = ['email', 'people', 'time', 'please']
  splittext = emailtext.split(" ")
  total = 0
  for word in spam_words:
    total += splittext.count(word) #appends the count of each of these words to total

  return total

После добавления этих двух функций мы должны увидеть в нашем Weka, что теперь мы близки к точности 79%. На самом деле есть много интересных вещей, на которые мы можем взглянуть, просто изучив реальные слова, которые мы видим. Мы можем обучить нашу машину словам или даже комбинациям слов в биграммах (парах слов) или триграммах (трех группах слов). Например, возможно, “бесплатно”-это слово, встречающееся как в спаме , так и в не-спаме электронной почте, но “бесплатные деньги” встречаются только в спам-письмах. Мы можем сделать функции, которые говорят, что “бесплатные деньги” – это спам, но “бесплатные” и “деньги” – это не спам.

Дополнительные функции

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

def all_caps(emailtext):
  return 1 if emailtext == emailtext.upper() else 0

def cap_ratio(emailtext):
  lowers = float(len([f for f in emailtext if f == f.lower()]))
  uppers = float(len([f for f in emailtext if f == f.upper()]))

  return uppers/lowers
мы выбираем атрибуты

Глядя на нашу Weka, мы видим, что all_caps не дает нам много информации, но данные cap_ratio , похоже, говорят нам о многом, и если мы посмотрим на распределение графиков, кажется, что все, что имеет отношение выше среднего, почти всегда является спамом.

распределение графа weka

Просто добавив функцию cap_ratio , наша точность подскочила почти до 86%.

мы выбираем атрибуты

Заключительные Слова

По мере добавления новых функций наша модель будет обучаться медленнее, потому что для изучения этих паттернов требуется время. К тому времени, когда мы закончим это упражнение, у нас будет 7 функций, что не так уж плохо.

У Weka есть отличная функция в Select attributes , где она предложит вам, какие функции следует использовать: Как вы можете видеть, она рекомендует нам использовать cap_ratio , has_html и spammy_words . Поэтому, если мы отменим выбор других функций на вкладке “Предварительный процесс”, а затем проверим точность в нашем классификаторе, мы, вероятно, получим что-то похожее на 86%, несмотря на то, что наша машина будет смотреть только на 3 функции.

мы выбираем атрибуты

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