65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Python 和 Keras 进行垃圾邮件分类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (5投票s)

2018年2月26日

CPOL

9分钟阅读

viewsIcon

18663

downloadIcon

398

如何准备训练和测试数据,定义一个简单的神经网络模型,并进行训练和测试

引言

机器学习使我们能够利用基于数据的数学和统计概率来确定代码的结果。这使得我们能够创建能够随时间“进化”的代码,因为它基于数据的变化,而不是具有特定的硬编码值或存储在某处的特定值。

例如,客户的信用卡使用情况会随着时间的推移而变化和演变,这取决于他们的购买习惯以及银行卡公司继续识别欺诈交易的需求。如果代码或数据库中设置了“阈值”,那么该值将需要定期更新,而对于大量客户而言,确定该值是多少将是极其昂贵/困难的。定期训练机器学习模型来识别基于实际数据的欺诈活动要更容易维护。

在本文中,我们将使用“监督学习”来确定一条消息是“垃圾邮件”还是“正常邮件”(垃圾邮件或非垃圾邮件)。监督学习意味着我们有一组数据,其中包含已识别为“垃圾邮件”或“正常邮件”的消息,我们将使用这些数据来训练机器学习模型,使其能够识别新消息是垃圾邮件还是正常邮件。这种判断基于新消息与我们用于训练模型的那些消息的统计相似性。

背景

如果您对编程有相当的熟悉度,并且对机器学习感兴趣,您应该能够跟上本教程。CodeProject 提供的数据如下所示

# Spam training data
Spam,<p>But could then once pomp to nor that glee glorious of deigned. The vexed times 
childe none native. To he vast now in to sore nor flow and most fabled. 
The few tis to loved vexed and all yet yea childe. Fulness consecrate of it before his 
a a a that.</p><p>Mirthful and and pangs wrong. Objects isle with partings ancient 
made was are. Childe and gild of all had to and ofttimes made soon from to long youth 
way condole sore.</p>
Spam,<p>His honeyed and land vile are so and native from ah to ah it like flash in not. 
That gild by in basked they lemans passed way who talethis forgot deigned nor friends 
his before strange. Found long little the. Talethis have soon of hellas had

一个指示“垃圾邮件”或“正常邮件”的初始值,后面跟着一个<p>标签,然后是消息的内容。此外,文件被分成训练和测试部分(稍后会详细介绍)。

导入库

在这里,与许多语言一样,我们导入代码所需的各种库。稍后我们将详细介绍我们正在使用的内容

import pandas as pd
import numpy as np

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.pipeline import FeatureUnion

from sklearn.linear_model.logistic import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
from sklearn.tree import DecisionTreeClassifier

from sklearn.utils import shuffle
from sklearn.metrics import precision_score, classification_report, accuracy_score

import time

加载和解析数据

是的,虽然成为一名21世纪最性感的数据科学家可能很诱人,但这需要大量时间来执行那些不那么性感的数据解析/清理/理解过程。对于这个项目,我大概有 85% 的时间都花在了这个上面。

def get_data():
    file_name = './SpamDetectionData.txt'
    rawdata = open(file_name, 'r')
    lines = rawdata.readlines()
    lines = lines[1:] #get rid of "header"
    spam_train = lines[0:1000]
    ham_train = lines[1002:2002]
    test_mix = lines[2004:]
    return (spam_train, ham_train, test_mix)

get_data()函数中,我们从CodeProject提供的文件中获取训练和测试数据。我们从文件中读取原始数据并将其存储在一个数组中。在类似以下的行中

  spam_train = lines[0:1000]

  ham_train = lines[1002:2002]

  test_mix = lines[2004:]

我们将数组的部分内容简单地分割到具有有意义名称的单独数组中。有关我们如何以及为何使用测试和训练数据的更多详细信息将在下面介绍。

创建 Pandas DataFrame

对于您在机器学习中将要进行的大多数数据/特征工程,您将使用Pandas,因为它提供了一套非常强大(但有时令人困惑)的工具来处理数据。在这里,我们正在创建一个DataFrame,您可以将其轻松地理解为内存中的“”,其中包含行和列,其中一列保存垃圾邮件/正常邮件消息的内容,另一列保存一个二进制标志(或数据科学术语中的类别),指示该消息是“垃圾邮件”(1)还是“正常邮件”(0)。

def create_dataframe(input_array):    
    spam_indcator = 'Spam,<p>'
    message_class = np.array([1 if spam_indcator in item else 0 for item in input_array])
    data = pd.DataFrame()
    data['class'] = message_class
    data['message'] = input_array
    return data

这是我们在下一步清理数据之前的 DataFrame 的样子 - 包括我们刚刚添加的“class”列。

数据清理前的 DataFrame

DataFame before the data is cleaned

删除单词和打乱数据

在这里,根据我在数据中看到的情况,我们想删除任何没有意义的无关文本,例如<p>,,或者,在本例中,CodeProject 提供的数据会提供明确的指示消息类型的明显数据,例如“ Ham,<p>”,这些在我们将要分类的实际消息中不会出现。当我们找到这些时,我们将简单地用空字符串替换它们。

words_to_remove = ['Ham,<p>', 'Spam,<p>', '<p>', '</p>', '\n']

def remove_words(input_line, key_words=words_to_remove):
    temp = input_line
    for word in key_words:
        temp = temp.replace(word, '')
    return temp

在这里,我们将上述过滤应用于我们的 DataFrame,然后打乱数据。在打乱数据时,CodeProject 提供的这些数据不是必需的,因为它们已经被分成了训练数据和测试数据。如果您要对另一个数据集执行此过程,您应该始终在将数据分成训练集和测试集之前打乱数据,以确保每个集合中都有大致相等数量的样本(在此情况下,为垃圾邮件和正常邮件)。如果这些集合不平衡,它们很容易导致训练/测试过程中的偏差。

 def remove_words_and_shuffle(input_dataframe, input_random_state=7):
    input_dataframe['message'] = input_dataframe['message'].apply(remove_words)
    messages, classes = shuffle(input_dataframe['message'], input_dataframe['class'], 
              random_state=input_random_state)
    df_return = pd.DataFrame()
    df_return['class'] = classes
    df_return['message'] = messages
    return df_return 

这是我们清理后的 DataFrame

DataFrame after it is cleaned up

训练和测试我们的模型

这就是一切的意义所在——使用训练数据来训练我们的机器学习模型,然后使用测试数据来确定模型的准确性以及它的表现如何。

 def test_models(X_train_input_raw, y_train_input, X_test_input_raw, y_test_input, models_dict):

    return_trained_models = {}
    
    return_vectorizer = FeatureUnion([('tfidf_vect', TfidfVectorizer())])
    
    X_train = return_vectorizer.fit_transform(X_train_input_raw)
    X_test = return_vectorizer.transform(X_test_input_raw)
    
    for key in models_dict:
        model_name = key
        model = models_dict[key]
        t1 = time.time()
        model.fit(X_train, y_train_input)
        t2 = time.time()
        predicted_y = model.predict(X_test)
        t3 = time.time()
        
        output_accuracy(y_test_input, predicted_y, model_name, t2 - t1, t3 - t2)        
        return_trained_models[model_name] = model
        
    return (return_trained_models, return_vectorizer)

这段代码涉及很多内容,所以我们将逐行进行。首先,让我们看看参数

  • X_train_input_data - 这些是我们将用于训练模型的“原始”垃圾邮件/正常邮件消息
  • y_train_input - 这是X_train_input_data参数的 0 或 1,表示正常邮件垃圾邮件
  • X_test_input_raw - 我们将用于测试训练模型准确性的“原始”垃圾邮件/正常邮件消息
  • y_test_input - 这是X_test_input_raw参数的 0 或 1,表示正常邮件垃圾邮件

return_trained_models = {} 是一个字典,将保存我们训练过的模型,以便以后使用

return_vectorizer = FeatureUnion([('tfidf_vect', TfidfVectorizer())]) 设置了一个TfidfVectorizer,用于应用于传入的消息。本质上,我们正在将一串单词(消息)转换为一个向量(数组),其中包含这些单词的出现次数。

此外,TF-IDF(文本频率-逆文档频率)会对一个术语在源文档中出现的频率进行加权。

 
tf - idf 值随着一个词在文档中出现次数的增加而增加,并通过该词在语料库中的频率进行抵消,这有助于调整某些词总体上出现频率较高的事实。如今,tf-idf 是最流行的术语加权方案之一;在数字图书馆领域的基于文本的推荐系统中,有 83% 使用 tf - idf。(来源

这意味着那些总体上出现频率较低,但在特定类型文档中出现频率较高的词将具有更高的权重。例如,“free”(免费)、“viagra”(伟哥)等词,它们在消息中(所有垃圾邮件正常邮件消息的总和)总体出现频率不高,但在垃圾邮件消息中出现频率很高,因此这些词将被赋予更高的权重,以表明该文档是垃圾邮件

可以设置和调整大量参数来提高模型的准确性 - 您可以在此处找到这些参数的详细信息。

接下来,既然我们已经创建了向量化器,我们将用训练消息“训练”它,并使用它将我们的测试消息集转换为向量

X_train = return_vectorizer.fit_transform(X_train_input_raw)    

X_test = return_vectorizer.transform(X_test_input_raw)

最后一步是循环遍历传入的模型字典,训练每个模型,使用模型预测测试数据,并输出每个模型的准确性。

输出训练模型的结果

当我们训练模型时,我们想看到模型的名称、训练模型所需的时间以及模型的准确性。此函数有助于做到这一点

 def output_accuracy(actual_y, predicted_y, model_name, train_time, predict_time):
    print('Model Name: ' + model_name)
    print('Train time: ', round(train_time, 2))
    print('Predict time: ', round(predict_time, 2))
    print('Model Accuracy: {:.4f}'.format(accuracy_score(actual_y, predicted_y)))
    print('Model Precision: {:.4f}'.format(precision_score(actual_y, predicted_y)))
    print('')
    print(classification_report(actual_y, predicted_y, digits=4))
    print("=========================================================================")

创建要测试的模型字典

在这里,我们创建了我们要训练和测试准确性的模型字典。您可以在此处添加更多模型进行测试,删除性能较差的模型,或更改模型的参数以确定哪个模型最适合您的需求。

def create_models():
    models = {}
    models['LinearSVC'] = LinearSVC()
    models['LogisticRegression'] = LogisticRegression()
    models['RandomForestClassifier'] = RandomForestClassifier()
    models['DecisionTreeClassifier'] = DecisionTreeClassifier()
    return models

整合

在这里,我们将所有步骤汇总在一起

  1. 获取数据并创建数据框。
  2. 清理并打乱数据。
  3. 将数据分为训练集和测试集的输入 (X) 和输出 (y)。
  4. 创建模型。
  5. 将模型和数据传递给test_models()函数以查看它们的性能。
spam_train, ham_train, test_mix = get_data()

words_to_remove = ['Ham,<p>', 'Spam,<p>', '<p>', '</p>', '\n']

df_train_cleaned = remove_words_and_shuffle(df_train)
df_test_cleaned = remove_words_and_shuffle(df_test)

X_train_raw = df_train_cleaned['message']
y_train = df_train_cleaned['class']

X_test_raw = df_test_cleaned['message']
y_test = df_test_cleaned['class']

X_test_raw = df_test_cleaned['message'] 
y_test = df_test_cleaned['class']

models = create_models()

trained_models, fitted_vectorizer = 
       test_models(X_train_raw, y_train, X_test_raw, y_test, models)

输出

当我们运行它时,这里是输出

Model Name: LinearSVC
Train time:  0.01
Predict time:  0.0
Model Accuracy: 1.0000
Model Precision: 1.0000

             precision    recall  f1-score   support

          0     1.0000    1.0000    1.0000        57
          1     1.0000    1.0000    1.0000        43

avg / total     1.0000    1.0000    1.0000       100

======================================================
Model Name: LogisticRegression
Train time:  0.01
Predict time:  0.0
Model Accuracy: 0.4300
Model Precision: 0.4300

             precision    recall  f1-score   support

          0     0.0000    0.0000    0.0000        57
          1     0.4300    1.0000    0.6014        43

avg / total     0.1849    0.4300    0.2586       100

======================================================
Model Name: DecisionTreeClassifier
Train time:  0.02
Predict time:  0.0
Model Accuracy: 0.9800
Model Precision: 0.9556

             precision    recall  f1-score   support

          0     1.0000    0.9649    0.9821        57
          1     0.9556    1.0000    0.9773        43

avg / total     0.9809    0.9800    0.9800       100

======================================================
Model Name: RandomForestClassifier
Train time:  0.02
Predict time:  0.0
Model Accuracy: 0.9800
Model Precision: 0.9556

             precision    recall  f1-score   support

          0     1.0000    0.9649    0.9821        57
          1     0.9556    1.0000    0.9773        43

avg / total     0.9809    0.9800    0.9800       100

======================================================

我们可以看到训练模型所需的时间、预测测试数据所需的时间,以及每个模型的准确性、精确率和召回率。其中一些术语需要进一步解释

  • 准确性 - 正确预测的观测值与总观测值的比率(对于我们来说,百分之多少的垃圾邮件/正常邮件消息被正确检测到)
  • 精确率 - 正确预测为正的观测值与所有预测为正的观测值的比率(在我们标识为垃圾邮件的消息中,有多少被正确识别为垃圾邮件
  • 召回率 - 正确预测为正的观测值与实际类别的所有观测值的比率(在所有实际是垃圾邮件的消息中,我们正确识别了多少)
  • F1 分数 - 精确率和召回率的加权平均值

是的,这些是需要花些精力去理解的棘手概念,解释起来甚至更棘手。这些解释是从这里借用的:http://blog.exsilio.com/,并结合了我将其与我们项目相关联的解释。请参考该页面,因为它提供了对这些主题更深入的讨论。

尝试使用您的消息进行模型测试

最后,让我们尝试用您自己的消息来测试,看看它们是否被正确地识别为垃圾邮件正常邮件

#from the sample ham and spam
ham = 'door beguiling cushions did. Evermore from raven from is beak shall name'
spam = 'The vexed times childe none native'
test_messages = [spam, ham]
transformed_test_messages = fitted_vectorizer.transform(test_messages)
trained_models['DecisionTreeClassifier'].predict(transformed_test_messages) 

输出是:

array([1, 0])

正确地识别了垃圾邮件正常邮件

结论

机器学习、深度学习和人工智能是未来,我们作为软件工程师需要理解并拥抱这些技术提供的力量,因为我们可以利用它们来更有效地解决我们工作中的公司和客户提出的并需要我们帮助解决的问题。

我有一个博客,专门帮助软件工程师理解和发展他们在机器学习、深度学习和人工智能领域的技能。如果您觉得从本文中学到了东西,欢迎访问我的博客 CognitiveCoder.com

感谢您一直阅读到最后。

历史

  • 2018 年 3 月 3 日 - 首次发布
  • 2018 年 3 月 3 日 - 修复了损坏的图片链接
© . All rights reserved.