【深度學習系列】PaddlePaddle垃圾郵件處理實戰(一)

PaddlePaddle垃圾郵件處理實戰(一)

背景介紹

  在咱們平常生活中,常常會受到各類垃圾郵件,譬如來自商家的廣告、打折促銷信息、澳門博彩郵件、理財推廣信息等,通常來講郵件客戶端都會設置必定的關鍵詞屏蔽這種垃圾郵件,或者對郵件進行歸類,可是總會有一些漏網之魚。
  不過,本身手動作一個垃圾郵件分類器也並非什麼難事。傳統的機器學習算法一般會採用樸素貝葉斯、支持向量機等算法對垃圾郵件進行過濾,今天咱們主要講如何用PaddlePaddle手寫一個垃圾郵件分類器。固然,在講PaddlePaddle作垃圾郵件處理以前,先回顧一下傳統的機器學習算法是如何對垃圾郵件進行分類的。python

瞭解數據集

  首先先了解一下今天的數據集:trec06c。trec06c是一個公開的垃圾郵件語料庫,由國際文本檢索會議提供,分爲英文數據集(trec06p)和中文數據集(trec06c),其中所含的郵件均來源於真實郵件保留了郵件的原有格式和內容。
文件下載地址:trec06c
文件格式:git

trec06c
│
└───data
│   │   000
│   │   001
│   │   ...
│   └───215
└───delay
│   │   index
└───full
│   │   index

文件內容:github

垃圾郵件示例:本公司有部分普通發票(商品銷售發票)增值稅發票及海關代徵增值稅專用繳款書及其它服務行業發票,公路、內河運輸發票。能夠以低稅率爲貴公司代開,本公司具備內、外貿生意實力,保證我司開具的票據的真實性。 但願能夠合做!共同發展!敬侯您的來電洽談、諮詢! 聯繫人:李先生 聯繫電話:13632588281 若有打擾望諒解,祝商琪。
正常郵件示例:講的是孔子後人的故事。一個老領導回到家鄉,跟兒子感情不和,跟貪財的孫子孔爲本和氣。老領導的弟弟魏宗萬是趕馬車的。有個洋妞大概是考察民俗的,在他們家過年。孔爲本總想出國,被爺爺教育了。最後,一家人基本和解。 順便問另外一類電影,北京青年電影製片廠的。算法

數據預處理

  拿到數據後咱們能夠很清楚的看到郵件的內容,但並非全部的內容都是咱們須要的,在這裏咱們僅提取了郵件中的中文來做爲訓練語料。若是仔細觀察的話,會發現不是全部的郵件都能直接打開,數據的編碼格式也須要轉換成utf-8格式方便咱們後面訓練使用。因此咱們須要對原始數據作一些數據預處理,包括如下幾個內容。bash

基本步驟

  • 轉換源數據編碼格式爲utf-8格式
  • 過濾字符
    • 去除全部非中文字符,如標點符號、英文字符、數字、網站連接等特殊字符。
  • 過濾停用詞
  • 對郵件內容進行分詞處理

訓練代碼

下面是具體的代碼 transfer.py:app

# -*- 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):
    for ch in check_str.decode('utf-8'):
        if u'\u4e00' <= ch <= u'\u9fff':
            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.write(line.encode("utf-8","ignore") + "\n")
            if label == "ham":
                ham_file.write(line.encode("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
                    else:
                        temp_list.append(word)
            line = " ".join(temp_list)
            fh1.write(line.encode("utf-8","ingore")+"\n")

if __name__=="__main__":
    file_path = sys.argv[1]
    label_path = sys.argv[2]
    stop_word_path = "stop_words.txt"
    word_vec_path = "word2vec.txt"
    spam_data = "spam.txt"
    ham_data = "ham.txt"
    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

運行結果:

  • ham.txt: 正樣本,正常郵件。共21373條數據。
    • 示例:我 就 鬧 不 明白 了 只要 你 本人 不介意 跟 你 爸爸媽媽 有 何干 爲啥 要說 呢 ..... 首先 謝謝 你們 安慰 我 。 可是 我 確實 很 難受 , 我 有 本身 的 苦衷 。 我 不敢 和 我 媽媽 說 的 這種 狀況 。 我 媽媽 是 那種 特別 容易 擔憂 的 那種 類型 。 並且 我 又 不 在 她 身邊 。 我家 是 外地 的 。 若是 和 媽媽 說 了 , 她 必定 不會 贊成 我 和 在 一塊兒 的 。 媽媽 對 身體健康 看 的 特別 重要 。 有 一年 姐夫 那年 常常 流鼻血 , 媽媽 都 特別 擔憂 , 老 催 姐姐 帶 着 去 看看 。
  • spam.txt: 負樣本,垃圾郵件。共41627條數據。
    • 示例:您好 如下 是 特別 爲 閣下 發 的 香港 信息 圖片 、 景點 等 不 知道 閣下 是否 喜 但願 沒有 打擾到 閣下 若是 沒法 看到 下面 內容 請 稍侯 或者 直接 進入 香港 行網 域名論壇 地址 真誠 爲您服務
  • word2vec.txt: 包含全部郵件分詞的內容,爲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.txt就能夠放到此處訓練word2vec模型生成詞向量了,具體實現代碼以下: word2vec.py測試

# -*- coding: utf-8 -*-
# Created by huxiaoman 2018.1.28
# word2vec.py:生成word2vec模型

import os
import sys
import numpy as np
from gensim.models.word2vec import Word2Vec
from gensim.corpora.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()

# word2vec.txt數據的地址
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

運行結果

0.73343221

小結

  本篇文章做爲用PaddlePaddle處理垃圾郵件實戰系列的預熱,主要講了如何對文本數據進行數據預處理與過濾,如何生成詞向量以及用傳統的機器學習方法--支持向量機訓練模型,獲得的準確率爲0.73343221。其結果的好壞取決於詞典的大小,詞向量維度的大小,svm的基本參數的調整,在實際操做過程當中還須要不斷的調參才能達到最優的效果。下一篇咱們將帶領你們如何用PaddlePaddle來作垃圾郵件處理,用深度學習的方法對垃圾郵件進行分類,看看效果是否比傳統的機器學習方法要更好,性能和速度是否能有必定的提高。

  • 本文受Modify的博文啓發所寫,全部含有Modify博文內容的部分都已通過Modify本人的贊成。本文首發於景略集智,並由景略集智製做成「PaddlePaddle調戲郵件詐騙犯」系列視頻。若是有不懂的,歡迎在評論區中提問~
相關文章
相關標籤/搜索