Привет! Сегодня я расскажу вам больше о том, что я сделал для создания спам-фильтра с использованием Наивного Байеса для обнаружения спам-данных из этого набора данных на kaggle с помощью машинного обучения UCI, а также с использованием TextAnalysis.jl на Юлии. .
Я начал с просмотра документов TextAnalysis.jl, чтобы лучше понять, как именно работает Классификатор NaiveBayes.
using TextAnalysis: NaiveBayesClassifier, fit!, predict m = NaiveBayesClassifier([:legal, :financial]) fit!(m, "this is financial doc", :financial) fit!(m, "this is legal doc", :legal) predict(m, "this should be predicted as a legal document")
Я запустил пример из документации и узнал, что функция NaiveBayesClassifier принимает в качестве аргумента массив возможных классов, которым могут принадлежать соответствующие данные.
В данном случае это были :legal
и :financial
. Я также узнал, что мы обучаем модель, сопоставляя соответствующие данные с функцией fit!
, где она принимает аргументы самой модели, строку данных, которые мы пытаемся обучить, и класс, к которому принадлежат данные. Данные здесь представляют собой строку, например “this is financial doc”
, а класс, к которому она принадлежит, в данном случае :financial
.
Наконец, я узнал, что функция predict
позволяет нам вводить строку данных и использует алгоритм NaiveBayesClassifier для предсказания того, к какому классу принадлежит строка, на основе строк данных, обученных до использования функции fit!
. Функция predict
принимает аргументы самой модели, а также строку данных, которые мы пытаемся предсказать.
Мой первый подход к проблеме заключался в том, что я подумал, что было бы неплохо сначала импортировать все данные. Имея опыт работы с пакетами CSV.jl и DataFrames.jl, я знаком с импортом данных.
using CSV, DataFrames spamdata = DataFrame(CSV.read("spam.csv"; allowmissing=:none)) --------------------------------------------------------------- julia> showall(spamdata) 5572×5 DataFrame │ Row │ v1 │ │ │ String │ ├──────┼────────┤ │ 1 │ ham │ │ 2 │ ham │ │ 3 │ spam │ │ 4 │ ham │ │ 5 │ ham │ │ 6 │ spam │ │ 7 │ ham │ │ 8 │ ham │ │ 9 │ spam │ │ 10 │ spam │
На этом изображении ниже показана структура исходного файла .csv
с данными.
CSV-файл имеет два столбца. v2
— это строка данных, которую мы хотим использовать для обучения, а v1
— это класс конкретной строки данных, соответствующей v2
.
Мне нужен способ перебрать каждую строку файла и разделить данные о ветчине в одном условии и данные о спаме в другом, чтобы, когда дело доходит до обучения, я мог обучать данные ham
с помощью функции fit!
, которая требует, чтобы я указал класс этих данных.
for row in eachrow(spamdata) if row.v1 == "ham" println("ham") elseif row.v1 == "spam" println("spam") end end
Это был успех, и я закончил тем, что напечатал ветчину и спам!
ham spam ham spam ham ham spam spam ham spam ⋮
Теперь, когда это не так, я могу определить свою модель с помощью функции NaiveBayesClassifier. Я хочу определить 2 класса, :ham
и :spam
.
using TextAnalysis: NaiveBayesClassifier, fit!, predict m = NaiveBayesClassifier([:ham, :spam])
Далее я хочу начать обучение моей модели. Как видно из исходной структуры файла .csv
, v2
— это строка, которую мы пытаемся обучить. Объединив цикл for
с функцией fit!
, я сделал следующее, чтобы попытаться обучить все имеющиеся у нас данные.
using CSV, DataFrames using TextAnalysis: NaiveBayesClassifier, fit!, predict spamdata = DataFrame(CSV.read("spam.csv"; allowmissing=:none)) global m = NaiveBayesClassifier([:ham, :spam]) for row in eachrow(spamdata) if row.v1 == "ham" fit!(m, row.v2, :ham) elseif row.v1 == "spam" fit!(m, row.v2, :spam) end end
Но когда я запустил это, я получил следующую ошибку: LoadError: Base.InvalidCharError{Char}('\xe5\xa3')
Именно тогда я понял, что набор данных содержит недопустимые символы в некоторых строках в столбце v2
. Чтобы устранить эту ошибку, нам нужно отфильтровать неподдерживаемые символы, используя следующую функцию: filter(isvalid, <string>)
for row in eachrow(spamdata) if row.v1 == "ham" fit!(m, filter(isvalid, row.v2), :ham) elseif row.v1 == "spam" fit!(m, filter(isvalid, row.v2), :spam) end end
Когда я заменил строку row.v2
на filter(isvalid, row.v2)
и снова запустил программу, ошибок не появилось. Поэтому модель успешно прошла обучение и пока все хорошо!
На самом деле в REPL ничего не распечатывалось, потому что не было ошибок и в программе ничего не распечатывалось. Чтобы полностью проверить, работает ли модель, мы можем попытаться создать прогноз и распечатать результаты прогноза, чтобы увидеть, действительно ли было проведено обучение или нет.
prediction1 = predict(m, "hello my name is kfung") prediction2 = predict(m, "text 31845 to get a free phone") println(prediction1) println(prediction2)
Здесь я создал два прогноза, первый из которых выглядит как сообщение электронной почты (поскольку оно не выглядит подозрительным), а второй — как спам-сообщение, чтобы проверить модель.
Результаты, которые я получил, приведены ниже:
Dict(:spam => 0.013170434049325023, :ham => 0.986829565950675) Dict(:spam => 0.9892304346396908, :ham => 0.010769565360309069)
Как мы можем сказать, предсказания были довольно точными, так как первое предсказание имело значение :ham
близкое к 1, что означает, что это, скорее всего, нежелательное сообщение, а второе предсказание имело :spam value
близкое к 1, что означает, что это, скорее всего, спам-сообщение. Именно то, что мы ожидали.
Но пользователи, которые не знакомы со словарями или синтаксисом Julia, могут не понимать, что означают приведенные выше словари. Я изменил код так, чтобы он проверял значения :spam
и :ham
в словаре и выводил класс большего значения из этих двух.
prediction = predict(m, "hello my name is kfung") if prediction[:spam] > prediction[:ham] println("spam") else println("ham") end
Как мы и ожидали, результатом работы этой программы была строка “ham”
, потому что предсказанное ею значение :ham
было больше, чем значение :spam
, следовательно, это, скорее всего, было неверным сообщением.
В конце концов, я завернул все в функцию, которая принимает строку в качестве аргумента, чтобы при вызове функции со строкой она выводила либо “spam”
, если модель предсказывает это как спам-сообщение, либо “ham”
, если модель предсказывает что это не спам-сообщение.
using CSV, DataFrames using TextAnalysis: NaiveBayesClassifier, fit!, predict function checkspam(msg::String) spamdata = DataFrame(CSV.read("spam.csv"; allowmissing=:none)) m = NaiveBayesClassifier([:ham, :spam]) for row in eachrow(spamdata) if row.v1 == "ham" fit!(m, filter(isvalid, row.v2), :ham) elseif row.v1 == "spam" fit!(m, filter(isvalid, row.v2), :spam) end end prediction = predict(m, msg) if prediction[:spam] > prediction[:ham] println("spam") else println("ham (not spam)") end end
В конце концов я понял, как я тренировал модель каждый раз, когда классифицировал сообщение. Это делает программу не очень эффективной во время выполнения, потому что каждый раз, когда мы пытаемся предсказать с помощью модели, она снова и снова проходит через 5600 строк данных. Вместо этого мы можем вывести модальность за пределы функции (чтобы она запускалась в начале только один раз) и сохранить модель в глобальной переменной, чтобы впоследствии она могла просто использовать предварительно обученную модель, хранящуюся в глобальной переменной, для классификации. любые другие сообщения.
using CSV, DataFrames using TextAnalysis: NaiveBayesClassifier, fit!, predict spamdata = DataFrame(CSV.read("spam.csv"; allowmissing=:none)) global m = NaiveBayesClassifier([:ham, :spam]) for row in eachrow(spamdata) if row.v1 == "ham" fit!(m, filter(isvalid, row.v2), :ham) elseif row.v1 == "spam" fit!(m, filter(isvalid, row.v2), :spam) end end function checkspam(msg::String) prediction = predict(m, msg) if prediction[:spam] > prediction[:ham] println("spam") else println("ham (not spam)") end end
В целом, я думаю, что это была очень успешная попытка создать новый спам-фильтр. Я узнал больше о том, как я могу применить Наивный Байес для моделирования независимых предположений для вероятностной классификации. В настоящее время наивный байесовский метод продолжает оставаться довольно популярным вариантом категоризации текста, примером которого является данный пример, и его можно противопоставить другим методам обучения моделей, таким как логистическая регрессия, при которой данные обучаются условно. У этой модели есть и другие приложения, такие как анализ настроений, на котором я скоро остановлюсь в другой статье, так что следите за обновлениями!
Большое спасибо за чтение!