• 本节通过垃圾邮件分类问题对系统学习系统设计展开描述。 下面分别是垃圾邮件和正常邮件: 1. 考虑哪些方法会起作用1.1 提取特征向量x=features of email (邮件的特征)x=features of email (邮件的特征) y=spam(1)...

    本节通过垃圾邮件分类问题对系统学习系统设计展开描述。
    下面分别是垃圾邮件和正常邮件:
    这里写图片描述

    1. 考虑哪些方法会起作用

    1.1 提取特征向量

    x=features of email (邮件的特征)x=features of email (邮件的特征)
    y=spam(1) or not spam(0) (是否为垃圾邮件:1是、0否)

    这里有一种选择邮件的一些特征变量的方法,比如说我们可能会想出一系列单词,我们可以认为这些单词能够用来区分垃圾邮件或非垃圾邮件。
    比如说,如果有封邮件,包含单词deal,那么它就很有可能是一封垃圾邮件,同时包含单词buy的邮件也很有可能是一封垃圾邮件,包含discount的邮件也很有可能是垃圾邮件。
    如果一封邮件中包含了我的名字,这有可能是一个知道我的人写的,说明这封邮件不太可能是垃圾邮件。因为某些原因,我认为now这个单词表明了这封邮件可能并不是垃圾邮件,因为我经常收到一些很紧急的邮件,当然还有别的单词。我们可以选出这样成百上千的单词。

    因此可以用一个特征向量x来表示这些词汇是否出现在了邮件中,从而建立特征向量
    这里写图片描述

    从何处入手来提高分类准确度?

    • 积累大量数据
      从直觉上讲,是要收集大量的数据。事实上确实好多人这么做,很多人认为收集越多的数据,算法就会表现的越好。事实上就垃圾邮件分类而言,有一个叫做“Honey Pot”的项目,它可以建立一个假的邮箱地址,故意将这些地址泄露给发垃圾邮件的人,这样就能收集到大量的垃圾邮件了。这样的话我们就能得到非常多的垃圾邮件来训练学习算法了。
    • 从email的header中提取路径等信息
      大量的数据可能会有帮助,但也可能没有。对于大部分的机器学习问题,还有很多办法用来提升机器学习效果。比如对于垃圾邮件来说,也许你会想到用更复杂的特征变量,像是邮件的路径信息(这种信息通常会出现在邮件的标题中)。因此,垃圾邮件发送方在发送垃圾邮件时,他们总会试图让这个邮件的来源变得模糊一些,或者是用假的邮件标题,或者通过不常见的服务器来发送邮件,用不常用的路由来发送邮件。
      因此可以通过邮件的标题部分来构造更加复杂的特征,来获得一系列的邮件路由信息,进而判定是不是一封垃圾邮件。
    • 从email的正文中提取特征
      你可能还会想到别的方法,比如从邮件的正文出发,寻找一些复杂点的特征,例如单词discount是否和单词discounts是一样的,又比如单词deal和dealer是否也应视为等同?甚至像这个例子中有的单词小写,有的单词大写。或者我们是否应该用标点符号来构造复杂的特征变量,因为垃圾邮件可能更多的使用感叹号。
    • 构建错词识别算法,来区分邮件中恶意拼写错误的单词
      同样的,我们也可能构造更加复杂的算法来检测或者纠正那些故意的拼写错误 ,例如m0rtgage,med1cine,w4tches。因为垃圾邮件发送方确实这么做了,因为如果你将4放到w4tches中,那么用我们之前提到的简单的方法,垃圾邮件分类器不会把w4tches和watches看成一样的,这样我们就很难区分这些故意拼错的垃圾邮件。发垃圾邮件的也很机智,他们这么做就逃避了一些过滤。

    NG“说”

    当我们使用机器学习时,总是可以“头脑风暴”一下,想出一堆方法来试试,就像这样。顺带一提,我有一段时间研究过垃圾邮件分类的问题,尽管我能够理解垃圾邮件分类的问题,我确实懂一些这方面的东西,但是我还是很难告诉你,这四种方法中,你最该去使用哪一种。
    事实上,坦白地说,最常见的情况是:一个研究小组,可能会随机确定其中的一个方法,但是有时候,这种方法并不是最有成效的。你知道,你只是随机选择了其中的一种方法。实际上,当你需要通过头脑风暴来想出不同方法来尝试去提高精度的时候,你可能已经超越了很多人了。令人难过的是,大部分人他们并不尝试着列出可能的方法。他们做的只是某天早上醒来,因为某些原因,有了一个突发奇想:”让我们来试试 用Honey Pot项目 收集大量的数据吧”。不管出于什么奇怪的原因,早上的灵机一动,还是随机选一个,然后干上大半年。
    但是我觉得我们有更好的方法,是的,我们将在随后的课程中讲到这个,那就是误差分析
    我会告诉你,怎样用一个更加系统性的方法从一堆不同的方法中选取合适的那一个。因此,你更有可能选择一个真正的好方法,能让你花上几天几周甚至是几个月去进行深入的研究。

    2. 误差分析

    这里写图片描述
    也就是
    1. 以一个你可以快速实现的简单的算法作为开始。实现它并且在交叉验证数据集上进行测试。
    2. 绘制出学习曲线来进而决定是否需要更多的数据,或者更多的特征,等等。
    3. 误差分析:主要检查出在你的算法下,有误差的(在交叉验证集中的)样本数据(手工检查)。具体如下:
    这里写图片描述

    如果你在构造一个学习算法,如果你能有一种评估你算法的方法,这将是非常有用的。
    假设我们试图决定是否把像”discount”,”discounts”,”discounter”,”discountring”这样的单词都视为等同。

    在自然语言处理中,这种方法是通过一种叫做词干提取的软件实现的。如果你想自己来试试,你可以在网上搜索“Porter
    Stemmer(波特词干提取法)”,这是在词干提取方面一个比较不错的软件。这个软件会将单词”discount”、”discounts”等词视为同一个单词。
    但是这类词干提取软件只会检查单词的开头几个字母,这虽然有用,但也可能会造成一些问题。举个例子,这个软件会把单词”universe(宇宙)”和”university(大学)”也视为同一个单词,因为他们的开头是一样的。

    当你在决定是否应该使用词干提取软件来分类时,这总是很难说清楚到底是好还是坏。特别地,误差分析也并不能帮助你决定词干提取是不是一个好的方法。

    与之相对地最好的方法来发现词干提取软件对你的分类器到底有没有用,是迅速的着手试一试,来看看它表现到底怎么样。
    这也是为什么迅速代码实现一个相对简单的模型的原因!!!

    2.3 小结

    NG“说”:

    当你在研究一个新的机器学习问题时,我总是推荐你实现一个较为简单快速即便不是那么完美的算法,我几乎从未见过人们是这样做。大家经常干的事情是花费大量的时间在构造算法上,构造他们以为的简单的方法,因此,不要担心你的算法太简单,或者太不完美,而是尽可能快地实现你的算法,当你有了初始的实现之后,它会变成一个非常有力的工具,来帮助你决定下一步的做法。因为我们可以先看看算法造成的错误,通过误差分析来看看它犯了什么错,然后来决定优化的方式。
    另一件事,假设你有了一个快速而不完美的算法实现,又有一个数值的评估数据,这会帮助你尝试新的想法快速地发现你尝试的这些想法是否能够提高算法的表现。从而你会更快地做出决定,在算法中放弃什么,吸收什么。

    展开全文
  • 在本教程中,我们将首先列出一个问题,然后使用一种称为Naive Bayes分类器的机器学习技术展示一个简单的解决方案。本教程需要一些编程和统计方面的经验,但不需要有机器学习经验。

    在本教程中,我们将首先列出一个问题,然后使用一种称为Naive Bayes分类器的机器学习技术展示一个简单的解决方案。本教程需要一些编程和统计方面的经验,但不需要有机器学习经验。

    垃圾邮件检测

    您在一家为数百万人提供电子邮件服务的公司中担任软件工程师。 最近,垃圾邮件一直是一个主要问题,已经导致您的客户离开。 您当前的垃圾邮件过滤器仅过滤掉客户先前标记为垃圾邮件的电子邮件。 但是,垃圾邮件发送者变得越来越聪明,为了阻止您的客户离开,您被分配了预测电子邮件是否为垃圾邮件的任务。

    培训和测试数据

    为了为此创建算法,您需要教您的程序垃圾邮件的外观(非垃圾邮件的外观)。 幸运的是,您有所有以前被客户标记为垃圾邮件的电子邮件。 您还需要一种测试垃圾邮件过滤器准确性的方法。 一种想法是对您用于训练的相同数据进行测试。 但是,这可能会导致ML中的一个主要问题,即过度拟合 这意味着您的模型过于偏向训练数据,并且可能无法在该训练集之外的元素上很好地工作。 避免这种情况的一种常见方法是将标记的数据拆分为70/30进行训练/测试。 这样可以确保您测试的数据与培训的数据不同。 重要的是要注意,您需要在数据集中混合使用垃圾邮件和非垃圾邮件数据,而不仅仅是垃圾邮件。 您确实希望您的训练数据尽可能与真实的电子邮件数据相似,我在这篇文章的底部链接了一个很好的数据集。

    贝叶斯定理

    贝叶斯定理在数学上表示为:

    https://en.wikipedia.org/wiki/Bayes'_theorem

    从本质上讲,它为我们提供了一种在无法直接测量条件概率的情况下计算条件概率的技巧。 例如,如果您要计算某人的年龄,则可以进行癌症研究,而不是进行全国性研究,只需获取有关年龄分布和癌症的现有统计数据,然后将其插入贝叶斯定理即可。 不用担心统计理论是否令人困惑,将其转换为代码时会更有意义。 但是,我还是建议您回过头再尝试稍后再理解它,因为未能理解贝叶斯定理是许多逻辑谬论的根源

    朴素贝叶斯分类器

    对于我们的问题,我们可以将A设置为电子邮件为垃圾邮件的可能性,将B设置为电子邮件的内容。 如果P(A | B)> P( ¬A | B),则可以将电子邮件分类为垃圾邮件,否则不能分类。 请注意,由于在两种情况下贝叶斯定理都会导致P(B)除数,因此我们可以将其从方程中删除以进行比较。 这留下:P(A)* P(B | A)> P( ¬A )* P(B | ¬A )。 计算P(A)和P( ¬A )是微不足道的,它们只是您的训练集中垃圾邮件与非垃圾邮件的百分比:

    #runs once on training data
    def train:
        total = 0
        numSpam = 0
        for email in trainData:
            if email.label == SPAM:
                numSpam += 1
            total += 1
        pA = numSpam/(float)total
        pNotA = (total — numSpam)/(float)total

    比较困难的部分是计算P(B | A)和P(B | ¬A )。 为了计算这些,我们将使用单词袋模型 。 这是一个非常简单的模型,将一段文本视为一包单独的单词,而无需注意它们的顺序。 对于每个单词,我们计算它在垃圾邮件和非垃圾邮件中出现的百分比。 我们称这个概率为P(B_i | A_x)。 一个具体的例子是为了计算P(free | spam),我们将计算所有组合的垃圾邮件中free单词出现的次数,并将其除以所有组合的垃圾邮件中单词的总数。 由于这些是静态值,因此我们可以在训练阶段对其进行计算。

    #runs once on training data
    def train:
        total = 0
        numSpam = 0
        for email in trainData:
            if email.label == SPAM:
                numSpam += 1
            total += 1
            processEmail(email.body, email.label)
        pA = numSpam/(float)total
        pNotA = (total — numSpam)/(float)total
    #counts the words in a specific email
    def processEmail(body, label):
        for word in body:
            if label == SPAM:
                trainPositive[word] = trainPositive.get(word, 0) + 1
                positiveTotal += 1
            else:
                trainNegative[word] = trainNegative.get(word, 0) + 1
                negativeTotal += 1
    #gives the conditional probability p(B_i | A_x)
    def conditionalWord(word, spam):
        if spam:
           return trainPositive[word]/(float)positiveTotal
        return trainNegative[word]/(float)negativeTotal

    要获得整个电子邮件的P(B | A_x),我们只需对电子邮件中每个单词i取P(B_i | A_x)值的乘积。 注意,这是在分类时而不是在最初训练时完成的。

    #gives the conditional probability p(B | A_x)
    def conditionalEmail(body, spam):
        result = 1.0
        for word in body:
            result *= conditionalWord(word, spam)
        return conditionalEmail

    我们终于有了将它们组合在一起所需的所有组件。 我们需要的最后一块是分类器,它会为每封电子邮件调用,并使用我们以前的功能对其进行分类。

    #classifies a new email as spam or not spam
    def classify(email):
        isSpam = pA * conditionalEmail(email, True) # P (A | B)
        notSpam = pNotA * conditionalEmail(email, False) # P(¬A | B)
        return isSpam > notSpam

    恭喜你! 您已经成功地从头开始编写了Naive Bayes分类器!

    但是,您需要进行一些更改以使其最佳运行并且没有错误:

    拉普拉斯平滑:
    我们没有提到的一件事是,如果您要分类的电子邮件中的单词不在您的训练集中,将会发生什么。 为了处理这种情况,我们需要添加一个平滑因子。 最好的示例在下面的修改代码中,其中添加了平滑因子alpha:

    #gives the conditional probability p(B_i | A_x) with smoothing
    def conditionalWord(word, spam):
        if spam:
           return (trainPositive.get(word,0)+alpha)/(float)(positiveTotal+alpha*numWords)
        return (trainNegative.get(word,0)+alpha)/(float)(negativeTotal+alpha*numWords)

    日志空间
    我们当前的实现严重依赖浮点乘法。 为了避免与很小的数字相乘的所有潜在问题,通常对等式执行对数以将所有乘法转换为加法。 我没有在示例代码中实现此功能,但强烈建议在实践中使用它。

    TF-IDF
    总体而言,用于文本分类的单词袋模型相当幼稚,可以通过诸如TF-IDF之类的其他东西加以改进。

    N-Grams
    我们可以做的另一项改进不仅是计算单个单词。 N-Grams是一种技术,其中可以考虑N个连续单词的集合并使用它们来计算概率。 这是有道理的,因为在英语中1克“好”传达的东西不同于2克“不好”​​。

    请注意,示例代码是为了获得最佳教学效果而非性能而编写的。 有一些明显的,微不足道的更改可以极大地提高其性能。

    样本数据集: https : //spamassassin.apache.org/publiccorpus/

    原文链接: https://hackernoon.com/how-to-build-a-simple-spam-detecting-machine-learning-classifier-4471fe6b816e

    展开全文
  • 一、垃圾邮件过滤技术项目需求与设计方案 二、数据的内容分析 (1、是否为垃圾邮件的标签,spam——是垃圾邮件;ham——不是垃圾邮件) (2、邮件的内容分析——主要包含:发件人、收件人、...

    一、垃圾邮件过滤技术项目需求与设计方案





    二、数据的内容分析

    (1、是否为垃圾邮件的标签,spam——是垃圾邮件;ham——不是垃圾邮件)


    (2、邮件的内容分析——主要包含:发件人、收件人、发件时间以及邮件的内容)



    三、需求分析、模型选择与架构




    四、数据清洗

    (一)·代码中应用的知识点

    (1)字典的get()函数


    (2)os.listdir()



    (二)数据清洗代码

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Author:ZhengzhengLiu
    
    import os
    
    #1、索引文件(分类标签)读取,该文件中分为两列
    #第一列:分类标签是否为垃圾邮件(是:spam、否:ham);
    # 第二列:存放邮件对应文件夹路径,两列之间通过空格分割
    def read_index_file(file_path):
        type_dict = {"spam":"1","ham":"0"}      #用字典存放垃圾邮件的分类标签
        index_file = open(file_path)
        index_dict = {}
        try:
            for line in index_file:  # 按行循环读取文件
                arr = line.split(" ")  # 用“空格”进行分割
                #pd.read_csv("full/index",sep=" ")      #pandas来写与上面等价
                if len(arr) == 2:       #分割完之后如果长度是2
                    key,value = arr     ##分别将spam  ../data/178/129赋值给key与value
                #添加到字段中
                value = value.replace("../data","").replace("\n","")    #替换
                # 字典赋值,字典名[键]=值,lower()将所有的字母转换成小写
                index_dict[value] = type_dict[key.lower()]      #
        finally:
            index_file.close()
        return index_dict
    
    #2、邮件的文件内容数据读取
    def read_file(file_path):
        # 读操作,邮件数据编码为"gb2312",数据读取有异常就ignore忽略
        file = open(file_path,"r",encoding="gb2312",errors="ignore")
        content_dict = {}
    
        try:
            is_content = False
            for line in file:  # 按行读取
                line = line.strip()  # 每行的空格去掉用strip()
                if line.startswith("From:"):
                    content_dict["from"] = line[5:]
                elif line.startswith("To:"):
                    content_dict["to"] = line[3:]
                elif line.startswith("Date:"):
                    content_dict["data"] = line[5:]
                elif not line:
                    # 邮件内容与上面信息存在着第一个空行,遇到空行时,这里标记为True以便进行下面的邮件内容处理
                    # line文件的行为空时是False,不为空时是True
                    is_content = True
    
                # 处理邮件内容(处理到为空的行时接着处理邮件的内容)
                if is_content:
                    if "content" in content_dict:
                        content_dict["content"] += line
                    else:
                        content_dict["content"] = line
        finally:
            file.close()
    
        return content_dict
    
    #3、邮件数据处理(内容的拼接,并用逗号进行分割)
    def process_file(file_path):
        content_dict = read_file(file_path)
    
        #进行处理(拼接),get()函数返回指定键的值,指定键的值不存在用指定的默认值unkown代替
        result_str = content_dict.get("from","unkown").replace(",","").strip()+","
        result_str += content_dict.get("to","unkown").replace(",","").strip()+","
        result_str += content_dict.get("data","unkown").replace(",","").strip()+","
        result_str += content_dict.get("content","unkown").replace(",","").strip()
        return result_str
    
    #4、开始进行数据处理——函数调用
    ## os.listdir    返回指定的文件夹包含的文件或文件夹包含的名称列表
    index_dict = read_index_file('../data/full/index')
    list0 = os.listdir('../data/data')      #list0是范围为[000-215]的列表
    # print(list0)
    for l1 in list0:    # l1:循环000--215
        l1_path = '../data/data/' + l1      #l1_path   ../data/data/215
        print('开始处理文件夹:' + l1_path)
        list1 = os.listdir(l1_path)     #list1:['000', '001', '002', '003'....'299']
        # print(list1)
        write_file_path = '../data/process01_' + l1
        with open(write_file_path, "w", encoding='utf-8') as writer:
            for l2 in list1:  # l2:循环000--299
                l2_path = l1_path + "/" + l2  # l2_path   ../data/data/215/000
                # 得到具体的文件内容后,进行文件数据的读取
                index_key = "/" + l1 + "/" + l2  # index_key:  /215/000
    
                if index_key in index_dict:
                    # 读取数据
                    content_str = process_file(l2_path)
                    # 添加分类标签(0、1)也用逗号隔开
                    content_str += "," + index_dict[index_key] + "\n"
                    # 进行数据输出
                    writer.writelines(content_str)
    
    # 再合并所有第一次构建好的内容
    with open('../data/result_process01', 'w', encoding='utf-8') as writer:
        for l1 in list0:
            file_path = '../data/process01_' + l1
            print("开始合并文件:" + file_path)
    
            with open(file_path, encoding='utf-8') as file:
                for line in file:
                    writer.writelines(line)
    
    

    (三)邮件存放路径框架与各步骤运行结果









    最后运行的结果:



    五、特征工程

    (一)邮件服务器处理


    知识点应用:





    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Author:ZhengzhengLiu
    
    import re
    import time
    import numpy as np
    import pandas as pd
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    
    ## 设置字符集,防止中文乱码
    mpl.rcParams['font.sans-serif']=[u'simHei']
    mpl.rcParams['axes.unicode_minus']=False
    
    
    # 1、文件数据读取
    df = pd.read_csv("../data/result_process01",sep=",",header=None,
                     names=["from","to","date","content","label"])
    # print(df.head())
    
    #2(1)、特征工程1 =>提取发件人和收件人的邮件服务器地址
    def extract_email_server_address(str1):
        it = re.findall(r"@([A-Za-z0-9]*\.[A-Za-z0-9\.]+)",str(str1))
        result = ""
        if len(it)>0:
            result = it[0]
        if not result:
            result = "unknown"
        return result
    
    df["to_address"] = pd.Series(map(lambda str:extract_email_server_address(str),df["to"]))
    df["from_address"] = pd.Series(map(lambda str:extract_email_server_address(str),df["from"]))
    # print(df.head(2))
    
    #2(2)、特征工程1 =>查看邮件服务器的数量
    print("=================to address================")
    print(df.to_address.value_counts().head(5))
    print("总邮件接收服务器类别数量为:"+str(df.to_address.unique().shape))
    
    print("=================from address================")
    print(df.from_address.value_counts().head(5))
    print("总邮件接收服务器类别数量为:"+str(df.from_address.unique().shape))
    
    from_address_df = df.from_address.value_counts().to_frame()
    len_less_10_from_adderss_count = from_address_df[from_address_df.from_address<=10].shape
    print("发送邮件数量小于10封的服务器数量为:"+str(len_less_10_from_adderss_count))



    (二)时间属性处理


    #3、特征工程2 =>邮件的时间提取
    def extract_email_date(str1):
        if not isinstance(str1,str):  #判断变量是否是str类型
            str1 = str(str1)    #str类型的强转
        str_len = len(str1)
    
        week = ""
        hour = ""
        # 0表示:上午[8,12];1表示:下午[13,18];2表示:晚上[19,23];3表示:凌晨[0,7]
        time_quantum = ""
    
        if str_len < 10:
            #unknown
            week = "unknown"
            hour = "unknown"
            time_quantum ="unknown"
            pass
        elif str_len == 16:
            # 2005-9-2 上午10:55
            rex = r"(\d{2}):\d{2}"  # \d  匹配任意数字,这里匹配10:55
            it = re.findall(rex,str1)
            if len(it) == 1:
                hour = it[0]
            else:
                hour = "unknown"
            week = "Fri"
            time_quantum = "0"
            pass
        elif str_len == 19:
            # Sep 23 2005 1:04 AM
            week = "Sep"
            hour = "01"
            time_quantum = "3"
            pass
        elif str_len == 21:
            # August 24 2005 5:00pm
            week = "Wed"
            hour = "17"
            time_quantum = "1"
            pass
        else:
            #匹配一个字符开头,+表示至少一次  \d 表示数字   ?表示可有可无  *? 非贪婪模式
            rex = r"([A-Za-z]+\d?[A-Za-z]*) .*?(\d{2}):\d{2}:\d{2}.*"
            it = re.findall(rex,str1)
            if len(it) == 1 and len(it[0]) == 2:
                week = it[0][0][-3]
                hour = it[0][1]
                int_hour = int(hour)
                if int_hour < 8:
                    time_quantum = "3"
                elif int_hour < 13:
                    time_quantum = "0"
                elif int_hour < 19:
                    time_quantum = "1"
                else:
                    time_quantum = "2"
                pass
            else:
                week = "unknown"
                hour = "unknown"
                time_quantum = "unknown"
        week = week.lower()
        hour = hour.lower()
        time_quantum = time_quantum.lower()
        return (week,hour,time_quantum)
    
    #数据转换
    data_time_extract_result = list(map(lambda st:extract_email_date(st),df["date"]))
    df["date_week"] = pd.Series(map(lambda t:t[0],data_time_extract_result))
    df["date_hour"] = pd.Series(map(lambda t:t[1],data_time_extract_result))
    df["date_time_quantum"] = pd.Series(map(lambda t:t[2],data_time_extract_result))
    print(df.head(2))
    
    print("=======星期属性字段描述======")
    print(df.date_week.value_counts().head(3))
    print(df[["date_week","label"]].groupby(["date_week","label"])["label"].count())
    
    print("=======小时属性字段描述======")
    print(df.date_hour.value_counts().head(3))
    print(df[['date_hour', 'label']].groupby(['date_hour', 'label'])['label'].count())
    
    print("=======时间段属性字段描述======")
    print(df.date_hour.value_counts().head(3))
    print(df[["date_time_quantum","label"]].groupby(["date_time_quantum","label"])["label"].count())
    
    #添加是否有时间
    df["has_date"] = df.apply(lambda c: 0 if c["date_week"] == "unknown" else 1,axis=1)
    print(df.head(2))




    (三)邮件内容分词——jieba分词



    #4、特征工程之三 => jieba分词操作
    
    #将文本类型全部转换为str类型,然后进行分词操作
    df["content"] = df["content"].astype("str")
    
    '''
    #1、jieba分词的重点在于:自定义词典
    #2、jieba添加分词字典,jieba.load_userdict("userdict.txt"),字典格式为:单词 词频(可选的) 词性(可选的)
    #   词典构建方式:一般都是基于jieba分词之后的效果进行人工干预
    #3、添加新词、删除词   jieba.add_word("")   jieba.del_word("")    
    #4、jieba.cut: def cut(self, sentence, cut_all=False, HMM=True)
    #   sentence:需要分割的文本,cut_all:分割模式,分为精准模式False、全分割True,HMM:新词可进行推测
    #5、长文本采用精准分割,短文本采用全分割模式
    #   一般在短文本处理过程中还需要考虑词性,并且还可能将分割好的单词进行组合
    #   词性需要导入的包:import jieba.posseg
    '''
    df["jieba_cut_content"] = list(map(lambda st:" ".join(jieba.cut(st)),df["content"]))    #分开的词用空格隔开
    print(df.head(2))

    运行结果为:


    注意内容:



    (四)邮件信息量/长度对是否为垃圾邮件的影响


    (1)应用知识点——groupby()技术

    详细参照链接:http://www.jianshu.com/p/2d49cb87626b


    在数据分析中,我们往往需要在将数据拆分,在每一个特定的组里进行运算。比如根据教育水平和年龄段计算某个城市的工作人口的平均收入。pandas中的groupby提供了一个高效的数据的分组运算。我们通过一个或者多个分类变量数据拆分,然后分别在拆分以后的数据上进行需要的计算。

           

    #5、特征工程之四 =>邮件长度对是否是垃圾邮件的影响
    def process_content_length(lg):
        if lg < 10:
            return 0
        elif lg <= 100:
            return 1
        elif lg <= 500:
            return 2
        elif lg <= 1000:
            return 3
        elif lg <= 1500:
            return 4
        elif lg <= 2000:
            return 5
        elif lg <= 2500:
            return 6
        elif lg <= 3000:
            return 7
        elif lg <= 4000:
            return 8
        elif lg <= 5000:
            return 9
        elif lg <= 10000:
            return 10
        elif lg <= 20000:
            return 11
        elif lg <= 30000:
            return 12
        elif lg <= 50000:
            return 13
        else:
            return 14
    
    df["content_length"] = pd.Series(map(lambda st:len(st),df["content"]))
    df["content_length_type"] = pd.Series(map(lambda st:process_content_length(st),df["content_length"]))
    #按照邮件长度类别和标签进行分组groupby,抽取这两列数据相同的放到一起,
    # 用agg和内置函数count聚合不同长度邮件分贝是否为垃圾邮件的数量,
    # reset_insex:将对象重新进行索引的构建
    df2 = df.groupby(["content_length_type","label"])["label"].agg(["count"]).reset_index()
    #label == 1:是垃圾邮件,对长度和数量进行重命名,count命名为c1
    df3 = df2[df2.label == 1][["content_length_type","count"]].rename(columns={"count":"c1"})
    df4 = df2[df2.label == 0][["content_length_type","count"]].rename(columns={"count":"c2"})
    df5 = pd.merge(df3,df4)  #数据集的合并,pandas.merge可依据一个或多个键将不同DataFrame中的行连接起来
    
    df5["c1_rage"] = df5.apply(lambda r:r["c1"]/(r["c1"]+r["c2"]),axis=1)   #按行进行统计
    df5["c2_rage"] = df5.apply(lambda r:r["c2"]/(r["c1"]+r["c2"]),axis=1)
    print(df5.head())
    
    #画图
    plt.plot(df5["content_length_type"],df5["c1_rage"],label=u"垃圾邮件比例")
    plt.plot(df5["content_length_type"],df5["c2_rage"],label=u"正常邮件比例")
    plt.xlabel(u"邮件长度标记")
    plt.ylabel(u"邮件比例")
    plt.grid(True)
    plt.legend(loc=0)
    plt.savefig("垃圾和正常邮件比例.png")
    plt.show()

    运行结果:


    (五)添加信号量

    #6、特征工程之五 ==> 添加信号量
    def precess_content_sema(x):
        if x>10000:
            return 0.5/np.exp(np.log10(x)-np.log10(500))+np.log(abs(x-500)+1)-np.log(abs(x-10000))+1
        else:
            return 0.5/np.exp(np.log10(x)-np.log10(500))+np.log(abs(x-500)+1)+1
    
    a = np.arange(1,20000)
    plt.plot(a,list(map(lambda t:precess_content_sema(t),a)),label=u"信息量")
    plt.grid(True)
    plt.legend(loc=0)
    plt.savefig("信息量.png")
    plt.show()
    
    df["content_sema"] = list(map(lambda st:precess_content_sema(st),df["content_length"]))
    print(df.head(2))
    #查看列名称
    print(df.dtypes)
    
    #获取需要的列,drop删除不需要的列
    df.drop(["from","to","date","content","to_address","from_address",
             "date_week","date_hour","date_time_quantum","content_length",
             "content_length_type"],1,inplace=True)
    print(df.info())
    print(df.head())
    
    #结果输出到CSV文件中
    df.to_csv("../data/result_process02",encoding="utf-8",index=False)

    运行结果:

                  




    六、模型效果评估


    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Author:ZhengzhengLiu
    
    import time
    import numpy as np
    import pandas as pd
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    
    from sklearn.feature_extraction.text import CountVectorizer,TfidfVectorizer
    from sklearn.model_selection import train_test_split
    from sklearn.decomposition import TruncatedSVD  #降维
    from sklearn.naive_bayes import BernoulliNB     #伯努利分布的贝叶斯公式
    from sklearn.metrics import f1_score,precision_score,recall_score
    
    ## 设置字符集,防止中文乱码
    mpl.rcParams['font.sans-serif']=[u'simHei']
    mpl.rcParams['axes.unicode_minus']=False
    
    #1、文件数据读取
    df = pd.read_csv("../data/result_process02",encoding="utf-8",sep=",")
    #如果有nan值,进行上删除操作
    df.dropna(axis=0,how="any",inplace=True)    #删除表中含有任何NaN的行
    print(df.head())
    print(df.info())
    
    #2、数据分割
    x_train,x_test,y_train,y_test = train_test_split(df[["has_date","jieba_cut_content","content_sema"]],
                                                     df["label"],test_size=0.2,random_state=0)
    print("训练数据集大小:%d" %x_train.shape[0])
    print("测试数据集大小:%d" %x_test.shape[0])
    print(x_train.head())
    
    #3、开始模型训练
    #3.1、特征工程,将文本数据转换为数值型数据
    transformer = TfidfVectorizer(norm="l2",use_idf=True)
    svd = TruncatedSVD(n_components=20)     #奇异值分解,降维
    jieba_cut_content = list(x_train["jieba_cut_content"].astype("str"))
    transformer_model = transformer.fit(jieba_cut_content)
    df1 = transformer_model.transform(jieba_cut_content)
    svd_model = svd.fit(df1)
    df2 = svd_model.transform(df1)
    
    data = pd.DataFrame(df2)
    print(data.head())
    print(data.info())
    
    #3.2、数据合并
    data["has_date"] = list(x_train["has_date"])
    data["content_sema"] = list(x_train["content_sema"])
    print("========数据合并后的data信息========")
    print(data.head())
    print(data.info())
    
    t1 = time.time()
    nb = BernoulliNB(alpha=1.0,binarize=0.0005) #贝叶斯分类模型构建
    model = nb.fit(data,y_train)
    t = time.time()-t1
    print("贝叶斯模型构建时间为:%.5f ms" %(t*1000))
    
    #4.1 对测试数据进行转换
    jieba_cut_content_test = list(x_test["jieba_cut_content"].astype("str"))
    data_test = pd.DataFrame(svd_model.transform(transformer_model.transform(jieba_cut_content_test)))
    data_test["has_date"] = list(x_test["has_date"])
    data_test["content_sema"] = list(x_test["content_sema"])
    print(data_test.head())
    print(data_test.info())
    
    #4.2 对测试数据进行测试
    y_predict = model.predict(data_test)
    
    #5、效果评估
    print("准确率为:%.5f" % precision_score(y_test,y_predict))
    print("召回率为:%.5f" % recall_score(y_test,y_predict))
    print("F1值为:%.5f" % f1_score(y_test,y_predict))
    


    运行结果:


               

                    





    展开全文
  • 学习了一点机器学习的内容:如何进行垃圾邮件分类。自己总结了一下。 试想一下我们人脑的思考方式,我们是如何判断一封邮件垃圾邮件的:如果邮件中包含某些词,而且这些词出现的频率较高时(比如一封很短的邮件...

    学习了一点机器学习的内容:如何进行垃圾邮件分类。自己总结了一下。
    试想一下我们人脑的思考方式,我们是如何判断一封邮件是垃圾邮件的:如果邮件中包含某些词,而且这些词出现的频率较高时(比如一封很短的邮件中只有几个Html标签,其中还包含“中奖”等词汇),我们把它归为“奇怪邮件”一类。当我们看了很多这样的“奇怪邮件”之后,我们判定这类邮件是我们所不需要的“垃圾邮件”,好在现在的邮箱都有垃圾箱可以帮我们分类。但是某一天我们收到腾讯客服的电话,我们真的中奖了,电话中说他们已经发了一封简短的邮件通知我,这是才焕然大悟,是不是那封邮件被当作垃圾邮件了,我们需要重新审视我们的分类标准。
    那我们来试着模拟人脑的这个过程:
    1,首先提取分类特征
    首先需要获得一个垃圾邮件样本,然后统计样本中所有邮件中每一个单词出现的频率,并进行排序。从而得到分类特征词。
    2,根据特征值计算新邮件是垃圾邮件的可能性。
    新邮件中出现了某些“垃圾邮件特征词”,如何断定它是不是垃圾邮件呢。我们可以通过计算它是垃圾邮件的概率来判断。即计算出现特征词w1,…wn时,该邮件是垃圾邮件的条件概率。
    介绍一下贝叶斯定理:
    下面的图片截取自:http://www.chepoo.com/naive-bayesian-text-classification-algorithm-to-learn.html
    贝叶斯定理
    最终得出:
    P(是垃圾邮件 | 含特征词w1,…wn) = P(含特征词w1 | 是垃圾邮件)*…P(wn | 是垃圾邮件) * P(是垃圾邮件) / (P(w1) * … P(wn))
    如果此概率大于50%,我们认为是垃圾邮件。
    注意:
    这里如果某个P(含特征词w1 | 是垃圾邮件)为0的话,会使得最终结果为0,这被称为数据稀疏问题。为了解决这个问题,我们让每个特征词最少出现一次。
    3,解决误杀和漏杀的问题。
    如果确定有误杀及漏杀的情况,如何改进此模型呢?
    这里猜测一下,是否可以通过调整P(w1 | 是垃圾邮件),来调整w1的判定权重呢?待以后学习后更新。

    展开全文
  • 垃圾邮件分类,作为初学者实践文本分类是一个不错的开始。文章将通过传统机器学习和深度学习的方法来解决分类问题。 机器学习方法:朴素贝叶斯、SVM、逻辑回归、RF、XGBoost、LightGBM 深度学习方法:

    垃圾邮件分类,作为初学者实践文本分类是一个不错的开始。文章将通过传统机器学习和深度学习的方法来解决分类问题。

    机器学习方法:朴素贝叶斯、SVM、逻辑回归、RF、XGBoost、LightGBM

    深度学习方法:垃圾邮件分类的各种尝试(深度学习篇)

    开源代码地址(欢迎star~):https://github.com/ljx02/Spam_Email_Classificaton

    数据集下载链接:由于数据较小,暂时也放到了Git项目中

    解决这个问题通用的思路是:

    • 读取文本数据,包括训练集、测试集、验证集,如果数据质量不高考虑先清洗一下数据
    • 创建词典,这一步有点抽象,可以看作是对语料中的词进行统计,方便后续提取特征
    • 特征提取,通过对特征进行编码(向量化)
    • 选择模型,开始训练分类器
    • 验证模型,预测结果

    数据集格式

    总的数据集一共有4458条数据,将按照8:2进行划分训练集和验证集。通过分析发现,其中pam的数量有3866条,占数据集的大多数,可以考虑不平衡样本采样进行训练。

    数据集的格式如图所示,有三列分别是ID,Label(pam、spam),Email

    在这里插入图片描述

    清洗数据集

    在实际中清洗数据也是非常必要的,套用一句俗话“数据决定了模型的上限”。常用的清洗数据的方法有:去掉停用词、去掉URL、去掉HTML标签、去掉特殊符号、去掉表情符号、去掉长重复字、将缩写补全、去掉单字、提取词干等等。当然,清洗数据也可能使模型变差,需要三思。提供部分处理的参考代码如下:

    - 去掉停用词

    from nltk.corpus import stopwords
    stop = set(stopwords.words('english'))
    
    
    text = "their are so many picture. how are you do this time very much!"
    clean_text = []
    for word in word_tokenize(text):
        if word not in stop:
            clean_text.append(word)
    print(clean_text)
    

    - 去掉URL

    # 删除URL
    example = "New competition launched :https://www.kaggle.com/c/nlp-getting-started"
    def remove_URL(text):
        url = re.compile(r'https?://\S+|www\.\S+')
        return url.sub(r'', text)
    
    
    print(remove_URL(example))
    

    - 去掉HTML标签

    # 删除HTML标签
    example = """<div>
    <h1>Real or Fake</h1>
    <p>Kaggle </p>
    <a href="https://www.kaggle.com/c/nlp-getting-started">getting started</a>
    </div>"""
    def remove_html(text):
        html = re.compile(r'<.*?>')
        return html.sub(r'', text)
    

    - 去掉表情符号

    # 删除表情符号
    def remove_emoji(text):
        emoji_pattern = re.compile("["
                                   u"\U0001F600-\U0001F64F"  # emoticons
                                   u"\U0001F300-\U0001F5FF"  # symbols & pictographs
                                   u"\U0001F680-\U0001F6FF"  # transport & map symbols
                                   u"\U0001F1E0-\U0001F1FF"  # flags (iOS)
                                   u"\U00002702-\U000027B0"
                                   u"\U000024C2-\U0001F251"
                                   "]+", flags=re.UNICODE)
        return emoji_pattern.sub(r'', text)
    

    - 去掉特殊符号

    import string
    def remove_punct(text):
        # 对punctuation中的词进行删除
        table = str.maketrans('', '', string.punctuation)
        return text.translate(table)
    

    使用机器学习的方法

    朴素贝叶斯、SVM

    1.1 读取数据

    import pandas as pd
    # 读邮件数据CSV
    train_email = pd.read_csv("data/train.csv", usecols=[2], encoding='utf-8')
    train_label = pd.read_csv("data/train.csv", usecols=[1], encoding='utf-8')
    

    1.2 构造词频矩阵

    第二步同时也构造了词典,因为词频矩阵的行是所有文章中出现的词,纵轴代表文章。统计每一篇文章中出现相应词的次数。举个例子如下:

    ['Hello','How are you','Are you OK']
    
    hello how are you
    0 1 0 0 0
    1 0 1 1 1
    2 0 0 1 1

    代码实现,使用sklearn中的工具,进行计算得出词频矩阵。这里需要注意的是,在处理训练集时,使用fit_transform而在测试得时候使用transform,因为测试的时候是不需要再训练。

    from sklearn.feature_extraction.text import CountVectorizer
    # 将内容转为list类型
    train_email = np.array(train_email).reshape((1, len(train_email)))[0].tolist()
    train_label = np.array(train_label).reshape((1, len(train_email)))[0].tolist()
    # 使用词袋模型
    vectorizer = CountVectorizer()
    # CountVectorizer类会把文本全部转换为小写,然后将文本词块化。主要是分词,分标点
    data_train_cnt = vectorizer.fit_transform(data_train)
    data_test_cnt = vectorizer.transform(data_dev)
    

    1.3 训练并预测

    from sklearn.naive_bayes import MultinomialNB
    from sklearn.svm import LinearSVC
    from sklearn.metrics import confusion_matrix
    # 利用贝叶斯的方法
    clf = MultinomialNB()
    clf.fit(data_train_cnt, label_train)
    score = clf.score(data_test_cnt, label_dev)
    print(score)
    
    # 利用SVM的方法
    svm = LinearSVC()
    svm.fit(data_train_cnt, label_train)
    score = svm.score(data_test_cnt, label_dev)
    print(score)
    
    result_svm = svm.predict(data_test_cnt)
    print(confusion_matrix(label_dev, result_svm))
    

    运行结果如下:分数都不错,总的感觉贝叶斯效果稍好一点(当然数据有限~~)

    在这里插入图片描述

    1.4 利用其它特征(TF-IDF)

    使用不同的特征进行训练,比较有名的是TF-IDF(词频-逆文档频率),逆文档频率含义是如果某个词或短语具有良好的类别区分能力,并且在其它文档中很少出现,则认为这个词或者短语有很好的类别区分能力,比较适合分类。通俗地讲,如果一个词在其中一个文档中出现过,在其它文章中没有出现过,则将这个词的权重增大。反之如果这个词大量出现在所有文档中,则表示这个词对于分类来说不是很重要,出现再多也无实际意义,所以降低其权重。计算逆文档频率一般采用下图公式:

    在这里插入图片描述

    TF-IDF就是词频和逆文档频率的乘积。

    具体代码如下(有两种写法):

    第一种:直接利用TfidfTransformer对词频矩阵进行计算,得出TF-IDF矩阵

    from sklearn.feature_extraction.text import TfidfTransformer
    
    vectorizer = CountVectorizer()
    # CountVectorizer类会把文本全部转换为小写,然后将文本词块化。主要是分词,分标点
    data_train_cnt = vectorizer.fit_transform(data_train)
    transformer = TfidfTransformer()
    data_train_tfidf = transformer.fit_transform(data_train_cnt)
    
    data_test_cnt = vectorizer.transform(data_dev)
    data_test_tfidf = transformer.transform(data_test_cnt)
    
    

    第二种:利用TfidfVectorizer对data_train直接操作,得出TF-IDF矩阵(最终结果是一样的)

    from sklearn.feature_extraction.text import TfidfTransformer
    from sklearn.feature_extraction.text import TfidfVectorizer
    
    vectorizer_tfidf = TfidfVectorizer(sublinear_tf=True)
    data_train_tfidf = vectorizer_tfidf.fit_transform(data_train)
    
    data_test_tfidf = vectorizer_tfidf.transform(data_dev)
    

    从结果分析,引入TF-IDF特征后效果有一点点波动,算做一种尝试。

    逻辑回归(LR)

    逻辑回归是用途最广的分类算法,适用性很强。但是想充分利用好逻辑回归,就得不断的调整参数,直到合理为止。具体代码和上边的极其相似,需要引入下面这句话:

    from sklearn.linear_model import LogisticRegression
    

    随机森林(RF)

    随机森林是一种以决策树为基础的一种更高级的算法。随机森林即可以用于回归也可以用于分类。随机森林从本质上看属于集成学习的一种,通过建立几个模型组合起来解决一个问题。往往随机森林的表现要优于单一的决策树。实现随机森林的代码与上边的类似,已经有库提供这个方法了,只需要引入下边代码:

    from sklearn.ensemble import RandomForestClassifier
    

    XGBoost

    XGBoost也属于集成学习,高效地实现了GBDT算法并进行了算法和工程上的许多改进。属于boosting的一种。XGBoost算法的核心思想如下:

    • 不断地添加树,每次添加都是要学习一个新的f(x),目的是拟合上次的残差
    • 每个叶子节点对应一个分数,最终只需要将对应的分数相加起来就得到样本的预测值。

    XGBoost的一大优势就是,能够自动学习出缺失值的处理策略。同时使用了一阶导数和二阶导数,有利于梯度下降的更快更准。

    当然XGBoost也有缺点:

    • 每轮迭代时,都需要遍历整个训练数据多次。耗时且占用较大内存。
    • 预排序方法的时间和空间的消耗都较大。

    同样,代码只需要引入库如下:

    import xgboost as xgb
    

    LightGBM

    LightGBM是一个梯度Boosting框架,使用基于决策树的学习方法。具有以下优势:

    • 更快的训练效率
    • 低内存的使用
    • 更高的准确率
    • 支持并行化学习
    • 可以处理大规模数据

    利用LightGBM进行训练,直接引库就好:

    import lightgbm as lgb
    

    总结

    尝试了不同的方法,总体不难,代码好多都有了方便的库函数辅助。但是每一种方法如果想发挥出它的最大效果,就得不断尝试修改参数。为了快捷调参,可以使用网格调参,具体使用方法已经写入代码中,可以查看GridSearchCV的使用方法。修改参数的第一步就是认识方法中的所有参数,下边我分享一些参数介绍的文章链接:

    LightGBM介绍及参数调优

    XGBoost介绍及参数调优

    LogisticRegression介绍及参数调优

    由于训练数据较少,最终发现NB的效果最理想。其它方法估计在大的数据集上会表现好一点,需要后续试验验证。后续可以改进的方法有:

    • 调整参数
    • 模型融合
    • 引入额外信息和特征
    展开全文
  • 贝叶斯里面的参数原理 最大似然: 即最符合观测数据的最有优势,即p(D|h)最大 奥卡姆剃刀:即越常见的越有可能发生,即p(h) 表示的是先验概率 最大似然: 当我们投掷一枚硬币,观测到的是正面,那么我们猜测投掷...
  • 本次实验是使用生成学习算法来处理数据(筛选垃圾邮件)。 判别学习算法(discriminative learning algorithm):直接学习p(y|x)(比如说logistic回归)或者说是从输入直接映射到{0,1}. 生成学习算法(generative ...
  • 机器学习垃圾邮箱机器学习,需要自动过滤算法、今日头条数据推荐 深度学习:2010至今。 机器学习包含深度学习 (可以理解为高级的机器学习),例如 图形图像识别、自然语言处理。 什么是机器学习...
  • 机器学习系统设计–垃圾邮件分类 假定我们现有一封邮件,其内容如下: From: cheapsales@buystufffromme.com To: ang@cs.stanford.edu Subject: Buy now! Deal of the week!Buy now! Rolex w4ches - $100 Med1cine ...
  • 这是英文版:Machine Learning for Hackers 中文版:R语言机器学习 第三章垃圾邮件分类的数据资源!其中我的博客会持续跟进学习!
  • 一, 数据清洗  (1),先做数据清洗,清洗过的数据被称之为"干净数据";  具体过程为-》要结合业务场景来判断哪些特征是值得被提取的,  如果自身对业务场景并不熟悉,可以咨询或者请教身边经验丰富的...
  • 机器学习我们一般可以分为两大类,模式识别和异常检测。从行为来看,模式识别和异常检测边界比较模糊。在模式识别中,我们试图发现隐藏在数据中的显式或潜在的特性,形成特征集进行分类判断。异常检测从另一个维度...
  • 在“程序清单4-5,文件解析及完整的垃圾邮件测试函数”代码中应将里面的setOfWords2Vec改为bagOfWords2VecMN 错误: UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 199: illegal ...
  • 1、本文代码是《机器学习实战》这本书的例程。点击下载《机器学习实战》及原书中的源代码; 2、运行环境为:window10+python3.2+各种python机器学习库。  安装可参考:点击打开链接 3、本文注重代码的实现过程,其
  • Spark机器学习垃圾邮件分类 步骤概述 通过HashingTF构建文本的特征向量,然后使用随机梯度下降算法实现逻辑回归,进而对邮件进行分类 垃圾邮件分类代码 导入相关的包 import org.apache.spark.mllib.regression....
  • 邮件分类过程中,代码报错,提示:UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 199: illegal multibyte sequence,错误原因出现不能解码的字符。 解决办法:email\ham\23.txt,找到...
  • 机器学习数据资源可用于朴素贝叶斯垃圾邮件过滤器中的一些训练文本数据集。使用朴素贝叶斯解决一些现实生活的问题时,需要先从文本内容得到字符串列表,然后生成词向量。其中朴素贝叶斯的一个最著名的应用:电子邮件...
  • 深入理解条件概率。详细讲解朴素贝叶斯的数学推导过程,能够使用原生代码完成朴素贝叶斯代码的编写。能够调用sklearn库完成朴素贝叶斯代码的编写。能够理解垃圾邮件分类原理并使用朴素贝叶斯完成垃圾邮件的分类。
  • 本文主要基于《机器学习实战--朴素贝叶斯》
  • 具体内容涉及Python必备机器学习库、线性回归算法原理推导、Python实现逻辑回归与梯度下降、案例实战,信用卡欺诈检测、决策树与集成算法、支持向量机原理推导、SVM实例与贝叶斯算法、机器学习常规套路与Xgboost算法...
1 2 3 4 5 ... 20
收藏数 24,214
精华内容 9,685