【深度学习系列】垃圾邮件处理实战(⼀)PaddlePaddle垃圾邮件处理实战(⼀)
背景介绍
  在我们⽇常⽣活中,经常会受到各种垃圾邮件,譬如来⾃商家的⼴告、打折促销信息、澳门邮件、理财推⼴信息等,⼀般来说邮件客户端都会设置⼀定的关键词屏蔽这种垃圾邮件,或者对邮件进⾏归类,但是总会有⼀些漏⽹之鱼。
  不过,⾃⼰⼿动做⼀个垃圾邮件分类器也并不是什么难事。传统的机器学习算法通常会采⽤朴素贝叶斯、⽀持向量机等算法对垃圾邮件进⾏过滤,今天我们主要讲如何⽤PaddlePaddle⼿写⼀个垃圾邮件分类器。当然,在讲PaddlePaddle做垃圾邮件处理之前,先回顾⼀下传统的机器学习算法是如何对垃圾邮件进⾏分类的。
了解数据
  ⾸先先了解⼀下今天的数据集:trec06c。trec06c是⼀个公开的垃圾邮件语料库,由国际⽂本检索会议提供,分为英⽂数据集
(trec06p)和中⽂数据集(trec06c),其中所含的邮件均来源于真实邮件保留了邮件的原有格式和内容。
⽂件下载地址:
⽂件格式:
trec06c
└───data
││  000
││  001
││  ...
│└───215
└───delay
││  index
└───full
││  index
⽂件内容:
垃圾邮件⽰例:本公司有部分普通发票(商品销售发票)增值税发票及海关代征增值税专⽤缴款书及其它服务⾏业发票,公路、内河运输发票。可以以低税率为贵公司代开,本公司具有内、外贸⽣意实⼒,保证我司开具的票据的真实性。希望可以合作!共同发展!敬侯您的来电洽谈、咨询!联系⼈:李先⽣:136******** 如有打扰望谅解,祝商琪。
正常邮件⽰例:讲的是孔⼦后⼈的故事。⼀个⽼领导回到家乡,跟⼉⼦感情不和,跟贪财的孙⼦孔为本和睦。⽼领导的弟弟魏宗万是赶马车的。有个洋妞⼤概是考察民俗的,在他们家过年。孔为本总想出国,被爷爷教育了。最后,⼀家⼈基本和解。顺便问另⼀类电影,北京青年电影制⽚⼚的。
数据预处理
  拿到数据后我们可以很清楚的看到邮件的内容,但并不是所有的内容都是我们需要的,在这⾥我们仅提取了邮件中的中⽂来作为训练语料。如果仔细观察的话,会发现不是所有的邮件都能直接打开,数据的编码格式也需要转换成utf-8格式⽅便我们后⾯训练使⽤。所以我们需要对原始数据做⼀些数据预处理,
包括以下⼏个内容。
基本步骤
转换源数据编码格式为utf-8格式
过滤字符
去除所有⾮中⽂字符,如标点符号、英⽂字符、数字、⽹站链接等特殊字符。
过滤停⽤词
对邮件内容进⾏分词处理
训练代码
下⾯是具体的代码 transfer.py:
# -*- coding: utf-8 -*-
#Created by huxiaoman 2018.1.28
#transfer.py:⽣成spam和ham数据
import jieba
import sys
import os
import re
# 判断邮件中的字符是否是中⽂
def check_contain_chinese(check_str):
return True
return False
# 加载邮件数据的label
def load_label_files(label_file):
label_dict ={}
for line in open(label_file).readlines():
list1 = line.strip().split("..")
label_dict[list1[1].strip()] = list1[0].strip()
return label_dict
# 加载停⽤词词表
def load_stop_train(stop_word_path):
stop_dict = {}
for line in open(stop_word_path).readlines():
line = line.strip()
stop_dict[line] = 1
return stop_dict
# 读取邮件数据,并转换为utf-8格式,⽣成spam和ham样本
def read_files(file_path,label_dict,stop_dict,spam_file_path,ham_file_path):    parents = os.listdir(file_path)
spam_file = open(spam_file_path,'a')
ham_file = open(ham_file_path,'a')
for parent in parents:
child = os.path.join(file_path,parent)
if os.path.isdir(child):
read_files(child,label_dict,stop_dict,spam_file_path,ham_file_path)        else:
print child[10:]
label = "unk"
if child[10:] in label_dict:
label = label_dict[child[10:]]
# deal file
temp_list = []
for line in open(child).readlines():
line = line.strip().decode("gbk",'ignore').encode('utf-8')
if not check_contain_chinese(line):
continue
seg_list = jieba.cut(line, cut_all=False)
for word in seg_list:
if word in stop_dict:
continue
else:
temp_list.append(word)
line = " ".join(temp_list)
print label
if label == "spam":
spam_file.de("utf-8","ignore") + "\n")
if label == "ham":
ham_file.de("utf-8","ignore")+"\n")
# ⽣成word2vec词表
def generate_word2vec(file_path,label_dict,stop_dict,word_vec):
parents = os.listdir(file_path)
fh1 = open(word_vec,'a')
i = 0
for parent in parents:
child = os.path.join(file_path,parent)
if os.path.isdir(child):
generate_word2vec(child,label_dict,stop_dict,word_vec)
else:
print child[10:]
i += 1
print i
label = "unk"
if child[10:] in label_dict:
label = label_dict[child[10:]]
# deal file
temp_list = []
for line in open(child).readlines():
line = line.strip().decode("gbk",'ignore').encode('utf-8')
if not check_contain_chinese(line):
continue
if len(line) == 0:
continue
seg_list = jieba.cut(line, cut_all=False)
for word in seg_list:
if word in stop_dict:
continue
fh1.de("utf-8","ingore")+"\n")
if __name__=="__main__":
file_path = sys.argv[1]
label_path = sys.argv[2]
stop_word_path = ""
word_vec_path = ""
spam_data = ""
ham_data = ""
label_dict = load_label_files(label_path)
stop_dict = load_stop_train(stop_word_path)
read_files(file_path,label_dict,stop_dict,spam_data,ham_data)
运⾏脚本
run.sh:
bashif [ $1 = "test" ]; then
echo "test"
python transfer.py ../test/ ../trec06c/full/index
else
echo "whole"
python transfer.py ../trec06c/data/ ../trec06c/full/index
fi
运⾏⽅式:
sh run.sh
运⾏结果:北京青年图片
<: 正样本,正常邮件。共21373条数据。
⽰例:我就闹不明⽩了只要你本⼈不介意跟你爸爸妈妈有何⼲为啥要说呢 ..... ⾸先谢谢⼤家安慰我。但是我确实很难受,我有⾃⼰的苦衷。我不敢和我妈妈说的这种情况。我妈妈是那种特别容易担⼼的那种类型。⽽且我⼜不在她⾝边。我家是外地的。如果和妈妈说了,她⼀定不会同意我和在⼀起的。妈妈对⾝体健康看的特别重要。
有⼀年夫那年经常流⿐⾎,妈妈都特别担⼼,⽼催带着去看看。
<: 负样本,垃圾邮件。共41627条数据。
⽰例:您好以下是特别为阁下发的⾹港信息图⽚、景点等不知道阁下是否喜希望没有打扰到阁下如果⽆法看到下⾯内容请稍侯或者直接进⼊⾹港⾏⽹域名论坛地址真诚为您服务
<: 包含所有邮件分词的内容,为Word2Vec提供训练预料。共63000条数据。
⽰例:我觉得,负债不要紧,最重要的是能负得起这个责任来,⽋了那么多钱,⾄少对当初拿出爱⼼来的⽹友们有个交待,还,还是不还了,或者,是有这个⼼但实在没能⼒,说明⼀声还都好不要连 ID 都不激活了,连⼿机号都换了 … … 别说外地的了,就连北京的⽹友都不到他 … … 他当时在⽔⽊ fl 版的那阵,我旁观了全过程。
⽣成词向量
传统⽅法的局限性
  我们知道,分词后的数据是不能直接拿到模型⾥去训练的,我们需要把词语转换成词向量才能进⾏模型的训练,这样⼀个词可以有⼀个多维的词向量组成。
  传统的⽅法是one-hot encoding,即⽤⼀个长向量来表⽰⼀个词,向量的长度为词典的⼤⼩,向量的分量只有⼀个1,其余全为0,1的位置即对应改词在词典中的位置,如电脑表⽰为:[0 0 0 0 0 1 0 0 0 0 ],⽿机表⽰为[0 0 0 0 0 0 0 1 0 ]这种⽅式如果采⽤稀疏存储,表达简洁,占⽤空间少,但是这种⽅法也
有⼏个缺点,⼀是容易受维数灾难的困扰,尤其是将其⽤于 Deep Learning的⼀些算法时;⼆是不能很好地刻画词与词之间的相似性,即任意两个词之间都是孤⽴的。光从这两个向量中看不出两个词是否有关系,损失⼤部分信息,导致结果会有较⼤偏差。
Word2Vec⽅法的优势
  在1968年Hinton⼜提出了Distributed REpresentation,可以解决One-hot encoding的缺点。其基本想法是直接⽤⼀个普通的向量表⽰⼀个词,这种向量⼀般长成这个样⼦:[0.792, −0.177, −0.107, 0.109, −0.542, ...],也就是普通的向量表⽰形式。维度以 50 维和 100 维⽐较常见。当然⼀个词怎么表⽰成这么样的⼀个向量需要通过训练得到,训练⽅法较多,word2vec是最常见的⼀种。需要注意的是,每个词在不同的语料库和不同的训练⽅法下,得到的词向量可能是不⼀样的。词向量⼀般维数不⾼,⼀般情况下指定1000、500维就可以了,所以⽤起来维数灾难的机会现对于one-hot representation表⽰就⼤⼤减少了。
  由于是⽤向量表⽰,⽽且⽤较好的训练算法得到的词向量的向量⼀般是有空间上的意义的,也就是说,将所有这些向量放在⼀起形成⼀个词向量空间,⽽每⼀向量则为该空间中的⼀个点,在这个空间上的词向量之间的距离度量也可以表⽰对应的两个词之间的“距离”。所谓两个词之间的“距离”,就是这两个词之间的语法,语义之间的相似性。
  ⼀个⽐较爽的应⽤⽅法是,得到词向量后,假如对于某个词A,想出这个词最相似的词,在建⽴好词向量后的情况,对计算机来说,只要拿这个词的词向量跟其他词的词向量⼀⼀计算欧式距离或者cos距离,得到距离最⼩的那个词,就是它最相似的。
  所以在这⾥我们选择了word2vec⽅法来训练⽣成词向量。关于word2vec的原理⼤家可以在⽹上搜索学习,此处不再赘述。
  在数据预处理中我们⽣成的就可以放到此处训练word2vec模型⽣成词向量了,具体实现代码如下: word2vec.py
# -*- coding: utf-8 -*-
# Created by huxiaoman 2018.1.28
# word2vec.py:⽣成word2vec模型
import os
import sys
import numpy as np
dels.word2vec import Word2Vec
pora.dictionary import Dictionary
import codecs
reload(sys)
sys.setdefaultencoding( "utf-8" )
class MySentences(object):
def __init__(self, dirname):
self.dirname = dirname
def __iter__(self):
for fname in os.listdir(self.dirname):
for line in codecs.open(os.path.join(self.dirname, fname),"r", encoding="utf-8",errors="ignore"):
yield line.strip().split()
# 数据的地址
train_path = "rawData/"
# ⽣成的word2vec模型的地址
model_path = "/modelPath/"
sentences = MySentences(train_path)
# 此处min_count=5代表5元模型,size=100代表词向量维度,worker=15表⽰15个线程
model = Word2Vec(sentences,min_count = 5,size=100,workers=15)
#保存模型
model.save(model_path+'/Word2vec_model.pkl')
运⾏⽅式
python word2vec.py
运⾏结果
Word2vec_model.pkl
模型训练
  ⽣成正负样本数据并将词语全部转化为词向量后我们就可以把数据灌倒模型⾥进⾏训练了,本篇中将采⽤传统的机器学习算法svm来进⾏训练。
具体步骤
加载数据集
划分训练集train、验证集val与测试集test
定义训练模型,并训练
验证准确率
实现代码
# 构建svm模型,加载数据等代码详见github
def get_svm_model(x_train,y_train,x_val,y_val):
model = SVC(C=1,kernel='rbf',max_iter=10,gamma=1,probability=True)
model.fit(x_train,y_train)
pred=model.predict(x_val)
fpr,tpr,thresholds = roc_curve(y_val, pred, pos_label=2)
score = metrics.f1_score(y_val,pred)
print score
运⾏⽅式
python train_svm.py
运⾏结果
⼩结
  本篇⽂章作为⽤PaddlePaddle处理垃圾邮件实战系列的预热,主要讲了如何对⽂本数据进⾏数据预处理与过滤,如何⽣成词向量以及⽤传统的机器学习⽅法--⽀持向量机训练模型,得到的准确率为0.73343221。其结果的好坏取决于词典的⼤⼩,词向量维度的⼤⼩,svm的基本参数的调整,在实际操作过程中还需要不断的调参才能达到最优的效果。下⼀篇我们将带领⼤家如何⽤PaddlePaddle来做垃圾邮件处理,⽤深度学习的⽅法对垃圾邮件进⾏分类,看看效果是否⽐传统的机器学习⽅法要更好,性能和速度是否能有⼀定的提升。
本⽂受Modify的博⽂启发所写,所有含有Modify博⽂内容的部分都已经过Modify本⼈的同意。本⽂⾸发于景略集智,并由景略集智制作成“PaddlePaddle调戏邮件犯”系列视频。如果有不懂的,欢迎在评论区中提问~