垃圾邮箱分类机器学习_垃圾邮件分类 机器学习 - CSDN
  • #coding:utf-8 ''' Created on 2017年3月19日@author: qiujiahao ...#贝叶斯实质上也是一个二分类def loadDataSet():#数据格式 postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
    #coding:utf-8
    '''
    Created on 2017年3月19日
    
    @author: qiujiahao
    '''
    
    
    import numpy as np
    #贝叶斯实质上也是一个二分类
    
    def loadDataSet():#数据格式
        postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                     ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                     ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
                     ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
                     ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
                     ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
        classVec = [0,1,0,1,0,1]#1 侮辱性文字 , 0 代表正常言论
        return postingList,classVec
    
    def createVocabList(dataset):#创建词汇表
        #set基本功能包括关系测试和消除重复元素
        vocabset = set([])
    
        for document in dataset:
            #取出dataset里的每一行,进行去重和合并操作,最后得到一个一维的数据
            vocabset = vocabset | set(document) #创建并集,实现去重的功能
        #将set数据转化为列表    
        return list(vocabset)
    
    def setOfWord2VecMN(vocabList,inputSet):
        #根据词汇表,将句子转化为向量
        #classVec中0的类别一条向量,类别为1的一条向量
        #生成一个全0的,和vocabList一样维度的列表,相同的index对应着对应的单词,
        #不同的值代表这个单词出现的次数
        returnVec = [0]*len(vocabList)
        for word in inputSet:
            if word in vocabList:
                returnVec[vocabList.index(word)] += 1
        return returnVec
    #训练:trainMatrix是转换好的向量,trainCategory是对应的类别
    def trainNB0(trainMatrix,trainCategory):
        numTrainDocs = len(trainMatrix)
        numWords = len(trainMatrix[0])
        pAbusive = sum(trainCategory)/float(numTrainDocs)
        p0Num = np.ones(numWords);p1Num = np.ones(numWords)#计算频数初始化为1
        p0Denom = 2.0;p1Denom = 2.0                  #即拉普拉斯平滑
        for i in range(numTrainDocs):
            if trainCategory[i]==1:
                p1Num += trainMatrix[i]
                p1Denom += sum(trainMatrix[i])
            else:
                p0Num += trainMatrix[i]
                p0Denom += sum(trainMatrix[i])
        #单词出现在第0类的概率和第1类的概率
        p1Vect = np.log(p1Num/p1Denom)#注意
        p0Vect = np.log(p0Num/p0Denom)#注意
        return p0Vect,p1Vect,pAbusive
    
    def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
        p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
        p0 = sum(vec2Classify * p0Vec) + np.log(1-pClass1)
        #在向量0和向量1中的概率高,那么就属于哪一类
        if p1 > p0:
            return 1
        else:
            return 0
    
    def testingNB():#流程展示
        listOPosts,listClasses = loadDataSet()#加载数据
        myVocabList = createVocabList(listOPosts)#建立词汇表
        trainMat = []
        for postinDoc in listOPosts:
            trainMat.append(setOfWord2VecMN(myVocabList,postinDoc))
        p0V,p1V,pAb = trainNB0(trainMat,listClasses)#训练
        #测试,指定一条新输入的语句
        testEntry = ['love','my','dalmation']
        thisDoc = setOfWord2VecMN(myVocabList,testEntry)
        print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)
    
        testEntry = ['stupid','dog']
        thisDoc = setOfWord2VecMN(myVocabList,testEntry)
        print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)
    
    if __name__ == "__main__":
        testingNB()
    ['love', 'my', 'dalmation'] classified as:  0
    ['stupid', 'dog'] classified as:  1
    
    展开全文
  • 垃圾邮件分类 机器学习 在本教程中,我们将首先提出一个问题,然后继续使用称为Naive Bayes分类器的机器学习技术展示一个简单的解决方案。 本教程需要一点编程和统计经验,但是不需要任何以前的机器学习经验。 ...

    垃圾邮件分类 机器学习

    在本教程中,我们将首先提出一个问题,然后继续使用称为Naive Bayes分类器的机器学习技术展示一个简单的解决方案。 本教程需要一点编程和统计经验,但是不需要任何以前的机器学习经验。

    垃圾邮件检测

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

    培训和测试数据

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

    贝叶斯定理

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

    https://zh.wikipedia.org/wiki/贝叶斯'_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之类的其他东西加以改进。

    N克
    我们可以做的另一项改进不仅是计算单个单词。 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),先做数据清洗,清洗过的数据被称之为"干净数据";  具体过程为-》要结合业务场景来判断哪些特征是值得被提取的,  如果自身对业务场景并不熟悉,可以咨询或者请教身边经验丰富的...


    一, 数据清洗 
    (1),先做数据清洗,清洗过的数据被称之为"干净数据";
        具体过程为-》要结合业务场景来判断哪些特征是值得被提取的,
        如果自身对业务场景并不熟悉,可以咨询或者请教身边经验丰富的人。

        举例:比较两句话的不同:
        ① 我司/代开/发票································1
        ② 月底/了/,/请/将/本月/发票/统一/装订/················0

    (2),数据清洗过程中,也可以将所有认为可能对结果产生影响的特征全部进行提取,
        并且逐一分析每个特征对结果的影响,再删除掉那些与结果无关的特征。
    (3),首先,根据label值,制作lable的正向字典
           其次,根据之前定义好的特征,分别提取非格式化邮件中的特征内容
           最后,讲所有提取到的特征转为行,并将216*300行内容保存至1个大的表格中。

    二, 特征工程
     (1), 对from & to 中的域名进行匹配,并且寻找它们与label值之间的关系,
             如果关系比较紧密,存在明显的关系,则进一步将它们转化为数值;
             如果关系不大,则降它们删除

      (2),  对date中的值进行正则匹配提取,并对匹配后的格式化数据进行时间段切分:
            8~13=0;13~19=1;19~24=2;24~8=3;分别取寻找时间段和label之间的关系,
            如果关系明确切明显,则就按照时间段作为特征来数值化特征值,否则删除。
            data长度为16的值类型:['2005-9-2 上午11:04', '2005-9-2 上午10:55', '2005-9-2 上午10:55', 
                                      '2005-9-2 上午10:55', '2005-9-2 上午10:55', '2005-9-2 上午10:55', 
                                      '2005-9-2 上午10:55']
            data长度为19的值类型:['Sep 23 2005 1:04 AM']
            data长度为21的值类型:['August 24 2005 5:00pm', 'August 24 2005 5:00pm', 'August 24 2005 5:00pm']
            
            通过分析,时间段值不能起到区分垃圾邮件的作用,但是如果是正常邮件一定含有日期;如果是垃圾邮件,
            则不一定含有,所以可以构造'has date'这一列为新的特征作为最后带入的特征进行训练及预测。
            
            对于邮件长度进行统计,发现特别短和特别长的邮件是垃圾邮件的概率非常大,因此可以拟合用e为底的指数和
            对数函数来表征邮件长度的信息量,尽管信息量的值是大于1的,但是其特点和概率类似,即:值越大则是垃圾
            邮件的概率就越大,值越小则是垃圾邮件的概率就越小。

            最后,要为邮件的内容进行分词。

    三、建模
        
        (1)分词后邮件内容的tf-idf转化(也可以用词袋法)。
         (2) 对tf-idf转化后的邮件内容做PCA 或者 svd 降维。
         (3) 把降维后的数据带入模型进行训练并测试。
     

    数据处理脚本 dataProcess.py

    #encoding:utf-8
    import os
    import sys#调试用包
    import time#计时包
    
    print("kaishi")
    def 制作标签字典(file_path):
        type_dict = {"spam": "1", "ham": "0"}
        index_file = open(file_path)#./date/full/index
        index_dict = {}#要创建的新字典
        try:
            for line in index_file:
                arr = line.split(" ")
                #[spam,../data/000/000]
                if len(arr) == 2:
                    key, value = arr
                    #(spam),(../data/000/000)
                # 添加到字段中
                value = value.replace("../data", "").replace("\n", "")
                #(spam),(/000/000)
                index_dict[value] = type_dict[key.lower()]
                #{/000/000: 1}
                #{/000/001: 0}
        finally:
            index_file.close()
        return index_dict
    # for file_path in path_list:
    # 邮件的文件内容数据读取
    def 字典化邮件文本内容(file_path):#dll
        './data/data/000/000'
        file = open(file_path, "r", encoding="gb2312", errors='ignore')
        content_dict = {}
        try:
            is_content = False#初始化为False后
            for line in file:
                #切掉两头的空格,逐步逼近格式化数据
                line = line.strip()
                if line.startswith("From:"):
                    #From: "yan"<(8月27-28,上海)培训课程>,
                    content_dict['from'] = line[5:]
                    # "yan"<(8月27-28,上海)培训课程>#
                    #{'from':"yan"<(8月27-28,上海)培训课程>}
                elif line.startswith("To:"):
                    content_dict['to'] = line[3:]
                    #lu@ccert.edu.cn
                elif line.startswith("Date:"):
                    content_dict['date'] = line[5:]
                    # Tue, 30 Aug 2005 10:08:15 +0800
                elif not line:
                    is_content = True
    
                # 处理邮件内容,利用is_content的真假来执行一个小的针对文本内容的for loop
                if is_content:
                    if 'content' in content_dict:
                        content_dict['content'] += line
                        #content_dict['content'] = content_dict['content']+line
                    else:
                        content_dict['content'] = line
        finally:
            file.close()
            '释放堆中的内存地址'
        return content_dict
    
    # 邮件数据处理
    def 字典转文本(file_path):
        #先把内容读成字典
        content_dict = 字典化邮件文本内容(file_path)#先把非格式化邮件转化为字典
    
        # 再把字典转成文本
        result_str = content_dict.get('from', 'unkown').replace(',', '').strip() + ","
        result_str += content_dict.get('to', 'unknown').replace(',', '').strip() + ","
        result_str += content_dict.get('date', 'unknown').replace(',', '').strip() + ","
        result_str += content_dict.get('content', 'unknown').replace(',', ' ').strip()
        return result_str
        
    #使用函数开始数据处理
    start = time.time()#开始时间标记
    index_dict = 制作标签字典('./data/full/index')
    #{/000/000: 1}
    #{/000/001: 0}
    # index_dict = 制作标签字典('C:\\Users/Administrator/Desktop/index')#('./data/full/index')
    # print(index_dict)
    # sys.exit(0)
    list0 = os.listdir('./data/data')#文件夹的名称
    #{000,001,002,215}
     
    for l1 in list0: #开始把N个文件夹中的file写入N*n个wiriter 
        '循环文件夹'
        l1_path = './data/data/' + l1#000
        #l1_path='./data/data/000',是文件夹的名称
        print('开始处理文件夹' + l1_path)#开始处理第000文件夹
        list1 = os.listdir(l1_path)#获取000文件夹内的所有文件名列表
        #[000,001]
        #list1文件列表
         
        write_file_path = './data/process01_' + l1
        #./data/process01_000
        #./data/process01_001
        #总共有216个
        #保存每个文件夹下面文件的文件 300行
        with open(write_file_path, "w", encoding= 'utf-8') as writer:
            for l2 in list1:
                '循环文件'
                l2_path = l1_path + "/" + l2#得到要处理文件的具体路径
                 
                index_key = "/" + l1 + "/" + l2
                 
                if index_key in index_dict:
                    #{/000/000: 1}
                    #{/000/001: 0}
                    content_str = 字典转文本(l2_path)
                    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)
    #两个for嵌套共执行:  216*300=6W+        
                
    end = time.time()
    print('数据处理总共耗时%.2f'%(end- start))           
    
        
            

    特征工程脚本 fetureExtract.py

    import pandas as pd
    import numpy as np
    
    
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    # import matplotlib.pyplot as plt
    import re
    import time
    import jieba
    import sys
    
    # mpl.rcParams['font.sans-serif'] = [u'simHei']#ָ改为指定字体“黑体”
    # mpl.rcParams['axes.unicode_minus'] = False #使得坐标轴保存负号变更为方块,用来正常显示负号
    # plt.title(u'我是中文')
    # get_ipython().magic(u'matplotlib tk')
    'from to  date content label'
    df = pd.read_csv('./data/result_process01', sep = ',', header = None, names= ['from','to', 'date', 'content','label'])
    # print(df.head(10))
    # print(df.tail(10))
    # print(df.info())
    # sys.exit("第20行")
    
    
    #分析邮件的收发地址对label的影响
    def 获取邮件收发地址(strl):#发送接收地址提取
        it = re.findall(r"@([A-Za-z0-9]*\.[A-Za-z0-9\.]+)", str(strl))#正则匹配
        #[^d]
        result = ''
    #     result =[]
    #     result = {}
        if len(it)>0:
            result = it[0]
        else:
            result = 'unknown'
        return result
    '''result0 = 获取邮件收发地址(df['from'])
    print(df['from'])
    print(df['from'].shape)
    print(result0)
    sys.exit(0)'''
    df['from_address'] = pd.Series(map(lambda str : 获取邮件收发地址(str), df['from']))#map映射并添加
    '''print(df.from_address.value_counts())
    sys.exit(0)'''
    df['to_address'] = pd.Series(map(lambda str: 获取邮件收发地址(str), df['to']))
    print("="*10 + 'to address' + "="*20)#也可以这样写
    print(df.to_address.value_counts().head(5))#
    print("总邮件接受服务器类别数量为:" + str(df.to_address.unique().shape))#计算服务器的个数
    print("="*10 + 'from address' + "= "*20)
    print(df.from_address.value_counts().head(10))
    print(df[['from_address', 'label']].groupby(['from_address', 'label'])['label'].count())
    print(df[['to_address', 'label']].groupby(['to_address', 'label'])['label'].count())
    print("邮件发送服务器类别数量为:" + str(df.from_address.unique().shape))
    from_address_df = df.from_address.value_counts().to_frame()#转为结构化的输出,带出列名
    len_less_10_from_address_count = from_address_df[from_address_df.from_address<=10].shape
    print("发送邮件数量小于10封的服务器数量为:" + str(len_less_10_from_address_count))
    # from_address_df[from_address_df.from_address<=10].to_csv('./data/fromToResult.csv')
    # df.from_address.value_counts().to_csv('./data/fromToResultNoneFrame.csv')
    #结论:from和to这两个特征没有用,最后要删除
    #===================================================================================================
    # np.unique(list(map(lambda t: len(str(t).strip()), df['date'])))#转换为list再去做
    print(np.unique(list(map(lambda t: len(str(t).strip()), df['date']))))
    # np.unique(list(filter(lambda t: len(str(t).strip())==30, df['date'])))
    print((list(filter(lambda t: len(str(t).strip())==3, df['date']))))
    #nan
    print((list(filter(lambda t: len(str(t).strip())==7, df['date']))))
    #unknown
    print((list(filter(lambda t: len(str(t).strip())==16, df['date']))))
    #2005-9-2 上午11:04
    print((list(filter(lambda t: len(str(t).strip())==19, df['date']))))
    #Sep 23 2005 1:04 AM
    print((list(filter(lambda t: len(str(t).strip())==21, df['date']))))
    #August 24 2005 5:00pm
    print((list(filter(lambda t: len(str(t).strip())==23, df['date']))))
    #Thu 1 Sep 2005 09:42:01
    print((list(filter(lambda t: len(str(t).strip())==24, df['date']))))
    #Mon 15 Aug 2005 07:04:08
    print((list(filter(lambda t: len(str(t).strip())==26, df['date']))))
    #Sat 1 Oct 2005 00:12:07 UT
    print((list(filter(lambda t: len(str(t).strip())==27, df['date']))))
    #Mon 1 Jan 2001 21:40:47 GMT
    print((list(filter(lambda t: len(str(t).strip())==28, df['date']))))
    #Sun 14 Aug 2005 11:59:22 GMT
    print((list(filter(lambda t: len(str(t).strip())==61, df['date']))))
    #[ 3  7 16 19 21 23 24 26 27 28 29 30 31 32 33 34 35 36 45 46 57 58 61 62]
    #通过打印,发现3, 7, 16, 19 21不含有星期数,需要观察的格式不全,需要特别处理
    def 根据日期长度提取日期特征(str1):#Tue 30 Aug 2005 10:08:15 +0800
        '''
        8~13=0;13~19=1;19~24=2;24~8=3;
        '''
        if not isinstance(str1, str):#如果不是字符串
            str1 = str(str1)
             
        str_len = len(str1)
        week = ""
        hour = ""
        time_quantum = ""      
        if str_len < 10:
            week = "unknown"
            hour = "unknown"
            time_quantum = "unknown"
            pass
        elif str_len == 16:#2005-9-2 上午11:04
            rex = r"(\d{2}):\d{2}"#只取冒号前面的
            it = re.findall(rex, str1)
            if len(it) == 1:
                hour = it[0]
            else:
                hour = "unknown"
            week = "Fri"
            time_quantum = "0"
            pass
            #['2005-9-2 上午11:04', '2005-9-2 上午10:55', '2005-9-2 上午10:55', '2005-9-2 上午10:55', '2005-9-2 上午10:55', '2005-9-2 上午10:55', '2005-9-2 上午10:55']
        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:               #'Fri 2 Sep 2005 08:17:50'  Wed 31 Aug 2005 15:06:36 
            rex = r"([A-Za-z]+\d?[A-Za-z]*) .*?(\d{2}):\d{2}:\d{2}.*"# 加问号保险些# 'Fri 23 Sep 2005 09:39:39 +0800 X-Priority: 3 X-Mailer: FoxMail'
            it = re.findall(rex, str1)
    #         print(it)
    #         print(len(it))
    #         print(len(it[0]))
    #         sys.exit('129')
            if len(it) == 1 and len(it[0]) ==2:
                week = it[0][0][-3:]#it是list 
                hour = it[0][1]
                int_hour = int(hour)
                #24~8=3;8~13=0;13~19=1;19~24=2;
                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)
    #数据转换
    date_time_extract_result = list(map(lambda st: 根据日期长度提取日期特征(st), df['date']))
    df['date_week'] = pd.Series(map(lambda t: t[0], date_time_extract_result))#匿名函数传出的是最后结构里面的值,是子集
    df['date_hour'] = pd.Series(map(lambda t: t[1], date_time_extract_result))
    df['date_time_quantum'] = pd.Series(map(lambda t: t[2], date_time_extract_result))
    print("======星期属性字段的描述==========")
    print(df.date_week.value_counts().head(3))
    print(df[['date_week', 'label']].groupby(['date_week', 'label'])['label'].count())#先取data_week 和 label,然后按照label去排
    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)#这里的1是按照行
    #结论:data数据对标签没有太大指示作用,但是,一般的垃圾邮件都不会含有时间
    # 开始分词==============================================
    print('='*30 + '现在开始分词 ,请 耐心等待 5分钟 。。。' + '='*20)
    df['content'] = df['content'].astype('str')#类型转换
    df['jieba_cut_content'] = list(map(lambda st: "  ".join(jieba.cut(st)), df['content']))
    df.head(4)    
    #特征工程之四 长度提取  
    def 邮件长度统计(lg):
        #以500为间隔递增
        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
    print(df['content'])
    df['content_length'] = pd.Series(map(lambda st:len(st),df['jieba_cut_content']))#content为切开之后的词向量 df['jieba_cut_content']
    df['content_length_type'] = pd.Series(map(lambda st: 邮件长度统计(st), df['content_length']))
    # print(df.head(10))  #如果不count就按照自然顺序排      
    df2 = df.groupby(['content_length_type', 'label'])['label'].agg(['count']).reset_index()#agg 计算并且添加count,类似于eval
    print(df2)
    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'})
    print(df3)
    print(df4)
    df5 = pd.merge(df3, df4)#注意pandas中merge与concat的区别
    df5['c1_rage'] = df5.apply(lambda r: r['c1'] / (r['c1'] + r['c2']), axis = 1)#1所占百分比
    df5['c2_rage'] = df5.apply(lambda r: r['c2'] / (r['c1'] + r['c2']), axis = 1)#0所占百分比
    print(df5) 
    #画图出来观测为信号添加做准备
    plt.plot(df5['content_length_type'], df5['c1_rage'], label = u'垃圾邮件比例')#长度与概率的图像
    plt.plot(df5['content_length_type'], df5['c2_rage'], label = u'正常邮件比例')
    plt.grid(True)
    plt.legend(loc = 0)#加入图例
    plt.show()
    #添加信号量,数值分析模拟回归方程
    def 长度信息量计算(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)
    a = np.arange(1, 20000)
    plt.plot(a, list(map(lambda t: 长度信息量计算(t) ,a)), label = u'信息量')
    # plt.plot(df['content_length'], list(map(lambda t: 长度信息量计算(t) ,df['content_length'])), label = u'信息量')
    plt.grid(True)
    plt.legend(loc = 0)
    plt.show()
    df['content_length_sema'] = list(map(lambda st: 长度信息量计算(st), df['content_length']))          
    # print(df.dtypes) #可以查看每一列的数据类型,也可以查看每一列的名称
       
    df.drop(['from', 'to', 'date', 'from_address', 'to_address', \
             'date_week','date_hour', 'date_time_quantum', 'content', \
             'content_length', 'content_length_type'], axis = 1, inplace=True) 
    # print(df.info())
    # print(df.head(10)) 
      
    df.to_csv('./data/result_process02', encoding='utf-8', index = False)
    df.to_csv('./data/result_process02.csv', encoding='utf-8', index = False)
    
            
              
        

    贝叶斯分类脚本bayes.py

    #encoding:utf-8
    import pandas as pd
    import numpy as np
    # import matplotlib as mpl
    # import matplotlib.pyplot as plt
    import sys
    import time
     
    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    from sklearn.decomposition import TruncatedSVD
    from sklearn.naive_bayes import BernoulliNB
    
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import f1_score, precision_score, recall_score
    
    # mpl.rcParams['font.sans-serif'] = [u'simHei']
    # mpl.rcParams['axes.unicode_minus'] = False
    
    df = pd.read_csv('./data/result_process02.csv', sep =',')
    # print(df.head(5))
    df.dropna(axis = 0, how ='any', inplace = True) #删除数据中有空值的实例
    # print(df.head(5))
    # print(df.info())
    
    x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                            'content_length_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(10))
    # print(x_test.head(10)) 
    #================================================================================================
    print('='*30 + '对分词后的油价内容做tf-idf转化' + '='*30)
    jieba_cut_content = list(x_train['jieba_cut_content'].astype('str'))
    transformer = TfidfVectorizer(norm = 'l2', use_idf = True)#加载tf-idf模型
    transformer_model = transformer.fit(jieba_cut_content)
    df1 = transformer_model.transform(jieba_cut_content)# fit_transform(jieba_cut_content)
    # df1 = transformer.fit_transform(jieba_cut_content)
    print('='*30 + '对tf-idf后的数值矩阵进行svd降维' + '='*30)
    svd = TruncatedSVD(n_components=20)#降成20维
    svd_model = svd.fit(df1)
    df2 = svd_model.transform(df1)
    data = pd.DataFrame(df2)
    
    print('='*30 + '合并处理后的矩阵' + '='*30)
    data['has_date'] = list(x_train['has_date'])
    data['content_length_sema'] = list(x_train['content_length_sema'])
    
    
    print('='*30 + '朴素贝叶斯模型的加载及训练' + '='*30)  
    nb = BernoulliNB(alpha = 1.0, binarize = 0.0005)
    model = nb.fit(data, y_train)#训练模型
    
    print('='*30 + '合并测试集数据矩阵' + '='*30)    
    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_length_sema'] = list(x_test['content_length_sema'])
    
    print('='*30 + '测试数据' + '='*30)
    start = time.time()  
    y_predict = model.predict(data_test)
    end = time.time()
    print('测试模型共消耗时间为:%0.2f'%(end-start))
    
    print('='*30 + '评估模型召回率' + '='*30)   
    precision = precision_score(y_test, y_predict)
    recall = recall_score(y_test, y_predict)
    f1mean = f1_score(y_test, y_predict)
    
    print('='*30 + '打印预测结果如下' + '='*30)   
    print('模型精确率为%0.5f' % precision)
    print('模型召回率为%0.5f' % recall)
    print('F1_mean为%0.5f' % f1mean)
    
    
    
    
    
    

    =========================下面为不同模型的测试============================

    decision_tree.py

    import pandas as pd
    import numpy as np
    import jieba
     
    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    from sklearn.decomposition import TruncatedSVD
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import f1_score, precision_score, recall_score
    
    df = pd.read_csv('./data/result_process02', sep =',')
    # print(df.head(5))
    df.dropna(axis = 0, how ='any', inplace = True) 
    print(df.head(5))
    print(df.info())
    
    x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                            'content_length_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(1000))
    # print(x_test.head(10)) 
    #================================================================================================
    print('='*30 + '开始训练集的特征工程' + '='*30)
    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(10))
    # print(data.info())
    
    data['has_date'] = list(x_train['has_date'])
    data['content_length_sema'] = list(x_train['content_length_sema'])
    # print(data.head(10))
    # print(data.info())
    
    tree = DecisionTreeClassifier(criterion='gini', max_depth = 5, random_state = 0)#'entropy'
    model = tree.fit(data, y_train)
    
    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_length_sema'] = list(x_test['content_length_sema'])
    # print(data_test.head(10))
    # print(data_test.info())
    #��ʼԤ��
    y_predict = model.predict(data_test)
    
    precision = precision_score(y_test, y_predict)
    recall = recall_score(y_test, y_predict)
    f1mean = f1_score(y_test, y_predict)
    
    print('精确率为:%0.5f' % precision)
    print('召回率为:%0.5f' % recall)
    print('F1均值为:%0.5f' % f1mean)
    
    # list01 = list(zip(data[0:5], tree.feature_importances_)) 
    # list02 = sorted(list01, key = lambda x: x[1], reverse = True)
    #  
    # print(list02)
    
    
    
    
    
    

    gradient_boost_decision_tree.py

    '''
    Created on 2018年1月26日
    
    @author: Administrator
    '''
    #-*- coding:utf-8 -*-
    import pandas as pd
    import numpy as np
    import jieba
     
    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    from sklearn.decomposition import TruncatedSVD
    from sklearn.ensemble import GradientBoostingClassifier
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import f1_score, precision_score, recall_score
    from sklearn.ensemble.tests.test_forest import check_min_samples_leaf
    
    df = pd.read_csv('./data/result_process02.csv', sep =',')
    # print(df.head(5))
    df.dropna(axis = 0, how ='any', inplace = True) 
    # print(df.head(5))
    # print(df.info())
    
    x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                            'content_length_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(1000))
    # print(x_test.head(10)) 
    #================================================================================================
    print('='*30 + '开始训练集的特征工程' + '='*30)
    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(10))
    # print(data.info())
    
    data['has_date'] = list(x_train['has_date'])
    data['content_length_sema'] = list(x_train['content_length_sema'])
    # print(data.head(10))
    # print(data.info())
    gbdt = GradientBoostingClassifier(learning_rate=0.01, n_estimators =100, max_depth=3,\
                                      min_samples_split = 50, loss = 'deviance', random_state = 0)
                                      #对数似然损失函数   指数损失函数exponential
    model = gbdt.fit(data, y_train)
    
    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_length_sema'] = list(x_test['content_length_sema'])
    # print(data_test.head(10))
    # print(data_test.info())
    #��ʼԤ��
    y_predict = model.predict(data_test)
    
    precision = precision_score(y_test, y_predict)
    recall = recall_score(y_test, y_predict)
    f1mean = f1_score(y_test, y_predict)
    
    print('精确率为:%0.5f' % precision)
    print('召回率为:%0.5f' % recall)
    print('F1均值为:%0.5f' % f1mean)
    
    
    
    
    
    
    

    k_nearest_neighbor.py

    '''
    Created on 2018年1月26日
    
    @author: Administrator
    '''
    import pandas as pd
    import numpy as np
    import jieba
     
    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    from sklearn.decomposition import TruncatedSVD
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import f1_score, precision_score, recall_score
    from sklearn.ensemble.tests.test_forest import check_min_samples_leaf
    
    df = pd.read_csv('./data/result_process02', sep =',')
    # print(df.head(5))
    df.dropna(axis = 0, how ='any', inplace = 'True') 
    print(df.head(5))
    print(df.info())
    
    x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                            'content_length_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(1000))
    print(x_test.head(10)) 
    #================================================================================================
    print('='*30 + '开始训练集的特征工程' + '='*30)
    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(10))
    print(data.info())
    
    data['has_date'] = list(x_train['has_date'])
    data['content_length_sema'] = list(x_train['content_length_sema'])
    print(data.head(10))
    print(data.info())
    
    knn = KNeighborsClassifier(n_neighbors=5)
    model = knn.fit(data, y_train)
    
    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_length_sema'] = list(x_test['content_length_sema'])
    print(data_test.head(10))
    print(data_test.info())
    #��ʼԤ��
    y_predict = model.predict(data_test)
    
    precision = precision_score(y_test, y_predict)
    recall = recall_score(y_test, y_predict)
    f1mean = f1_score(y_test, y_predict)
    
    print('精确率为:%0.5f' % precision)
    print('召回率为:%0.5f' % recall)
    print('F1均值为:%0.5f' % f1mean)
    
    
    
    
    
    
    

    random_forest.py

    '''
    Created on 2018年1月26日
    
    @author: Administrator
    '''
    import pandas as pd
    import numpy as np
    import jieba
     
    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    from sklearn.decomposition import TruncatedSVD
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import f1_score, precision_score, recall_score
    from sklearn.ensemble.tests.test_forest import check_min_samples_leaf
    
    df = pd.read_csv('./data/result_process02.csv', sep =',')
    # print(df.head(5))
    df.dropna(axis = 0, how ='any', inplace = True) 
    # print(df.head(5))
    # print(df.info())
    
    x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                            'content_length_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(1000))
    # print(x_test.head(10)) 
    #================================================================================================
    print('='*30 + '开始训练集的特征工程' + '='*30)
    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(10))
    # print(data.info())
    
    data['has_date'] = list(x_train['has_date'])
    data['content_length_sema'] = list(x_train['content_length_sema'])
    # print(data.head(10))
    # print(data.info())
    
    forest = RandomForestClassifier(n_estimators=100, criterion='gini', max_depth=3, random_state=0)
    model = forest.fit(data, y_train)
    
    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_length_sema'] = list(x_test['content_length_sema'])
    # print(data_test.head(10))
    # print(data_test.info())
    #��ʼԤ��
    y_predict = model.predict(data_test)
    
    precision = precision_score(y_test, y_predict)
    recall = recall_score(y_test, y_predict)
    f1mean = f1_score(y_test, y_predict)
    
    print('精确率为:%0.5f' % precision)
    print('召回率为:%0.5f' % recall)
    print('F1均值为:%0.5f' % f1mean)
    
    
    
    
    
    
    

    support_vector_machine.py

    '''
    Created on 2018年1月26日
    
    @author: Administrator
    '''
    import pandas as pd
    import numpy as np
    import jieba
     
    from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
    from sklearn.decomposition import TruncatedSVD
    from sklearn.svm import SVC
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import f1_score, precision_score, recall_score
    from sklearn.ensemble.tests.test_forest import check_min_samples_leaf
    
    df = pd.read_csv('./data/result_process02', sep =',')
    # print(df.head(5))
    df.dropna(axis = 0, how ='any', inplace = 'True') 
    print(df.head(5))
    print(df.info())
    
    x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                            'content_length_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(1000))
    print(x_test.head(10)) 
    #================================================================================================
    print('='*30 + '开始训练集的特征工程' + '='*30)
    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(10))
    print(data.info())
    
    data['has_date'] = list(x_train['has_date'])
    data['content_length_sema'] = list(x_train['content_length_sema'])
    print(data.head(10))
    print(data.info())
    
    svm = SVC(C = 1, kernel='rbf', degree = 3, gamma = 0.001)
    model = svm.fit(data, y_train)
    
    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_length_sema'] = list(x_test['content_length_sema'])
    print(data_test.head(10))
    print(data_test.info())
    #��ʼԤ��
    y_predict = model.predict(data_test)
    
    precision = precision_score(y_test, y_predict)
    recall = recall_score(y_test, y_predict)
    f1mean = f1_score(y_test, y_predict)
    
    print('精确率为:%0.5f' % precision)
    print('召回率为:%0.5f' % recall)
    print('F1均值为:%0.5f' % f1mean)
    
    
    
    
    
    
    

     

    展开全文
  • 垃圾邮件分类,作为初学者实践文本分类是一个不错的开始。文章将通过传统机器学习和深度学习的方法来解决分类问题。 机器学习方法:朴素贝叶斯、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的效果最理想。其它方法估计在大的数据集上会表现好一点,需要后续试验验证。后续可以改进的方法有:

    • 调整参数
    • 模型融合
    • 引入额外信息和特征
    展开全文
  • Spark机器学习垃圾邮件分类 步骤概述 通过HashingTF构建文本的特征向量,然后使用随机梯度下降算法实现逻辑回归,进而对邮件进行分类 垃圾邮件分类代码 导入相关的包 import org.apache.spark.mllib.regression....
  • 机器学习系统设计–垃圾邮件分类 假定我们现有一封邮件,其内容如下: From: cheapsales@buystufffromme.com To: ang@cs.stanford.edu Subject: Buy now! Deal of the week!Buy now! Rolex w4ches - $100 Med1cine ...
  • 学习了一点机器学习的内容:如何进行垃圾邮件分类。自己总结了一下。 试想一下我们人脑的思考方式,我们是如何判断一封邮件是垃圾邮件的:如果邮件中包含某些词,而且这些词出现的频率较高时(比如一封很短的邮件...
  • 在本教程中,我们将首先列出一个问题,然后使用一种称为Naive Bayes分类器的机器学习技术展示一个简单的解决方案。本教程需要一些编程和统计方面的经验,但不需要有机器学习经验。
  • 这是英文版:Machine Learning for Hackers 中文版:R语言机器学习 第三章垃圾邮件分类的数据资源!其中我的博客会持续跟进学习!
  • 秒懂机器学习---朴素贝叶斯进行垃圾邮件分类最最最简单实战 一、总结 一句话总结: 用最简单的实例来演示 算法是最方便的能洞悉算法实质的方式 注意有多个词表,而不是一个混合的词表 1、朴素贝叶斯进行垃圾...
  • 垃圾邮件分类是监督学习分类中一个最经典的案例,本文先复习了基础的概率论知识、贝叶斯法则以及朴素贝叶斯模型的思想,最后给出了垃圾邮件分类在Matlab中用朴素贝叶斯模型的实现
  • 贝叶斯原理1、其最根本的原理同样是利用统计学知识进行推导的,实际就是计算某些特征的组合为某一类的概率,根据概率大的来判断属于哪一类别,这样解释是不是有点抽象,没关系,下面举一个小小的例子就能很快理解了2...
  • 本节通过垃圾邮件分类问题对系统学习系统设计展开描述。 下面分别是垃圾邮件和正常邮件: 1. 考虑哪些方法会起作用1.1 提取特征向量x=features of email (邮件的特征)x=features of email (邮件的特征) y=spam(1)...
  • 机器学习笔记 教材为:机器学习:实用案例解析 代码: github链接 本文代码有微小修改1. bayes 垃圾分类器1.1加载路径共有三类邮件: 1. 易识别的正常邮件:easy_ham. 2. 不易识别的正常邮件:hard_ham. 3. 垃圾...
  • 学习了那么多机器学习模型,一切都是为了实践,动手自己写写这些模型的实现对自己很有帮助的,坚持,共勉。本文主要致力于总结贝叶斯实战中程序代码的实现(python)及朴素贝叶斯模型原理的总结。python的numpy包...
  • 项目描述:将收到英文的邮件分类,类似于邮箱垃圾过滤器的功能 工具:MATLAB 主要完成工作:1、对邮件数据预处理;2、提取邮件特征 补充说明:怎么对SVM中的参数调参? 选定一个初始值,以三倍的增长率进行增加...
  • 朴素贝叶斯分类器介绍概述  朴素贝叶斯分类器技术基于贝叶斯定理,特别适用于输入维数较高的情况。尽管朴素贝叶斯方法简单,但它通常比更复杂的分类方法更胜一筹。    为了演示朴素贝叶斯分类的概念,请考虑...
  • 6.1.SVM建立垃圾邮件分类器 1)题目: 如今,许多电子邮件服务提供垃圾邮件过滤器,能够将电子邮件精确地分类为垃圾邮件和非垃圾邮件。在本部分练习中,您将使用SVM构建自己的垃圾邮件过滤器。 您将训练一个分类器来...
  • 机器学习我们一般可以分为两大类,模式识别和异常检测。从行为来看,模式识别和异常检测边界比较模糊。在模式识别中,我们试图发现隐藏在数据中的显式或潜在的特性,形成特征集进行分类判断。异常检测从另一个维度...
1 2 3 4 5 ... 20
收藏数 17,132
精华内容 6,852
关键字:

垃圾邮箱分类机器学习