垃圾郵件分類實戰(SVM)


1. 數據集說明

trec06c是一個公開的垃圾郵件語料庫,由國際文本檢索會議提供,分為英文數據集(trec06p)和中文數據集(trec06c),其中所含的郵件均來源於真實郵件保留了郵件的原有格式和內容,下載地址:https://plg.uwaterloo.ca/~gvcormac/treccorpus06/

由於數據集分散在各個文件中,為了方便我將正樣本和負樣本分別放在了ham_data和spam_data文件夾中(處女座的強迫症)

正樣本數:21766

負樣本數:42854

中文停用詞:chinese_stop_vocab.txt

下面使用的所有數據集都已上傳github

2. 實現思路

  1. 對單個郵件進行數據預處理

    • 去除所有非中文字符,如標點符號、英文字符、數字、網站鏈接等特殊字符
    • 對郵件內容進行分詞處理
    • 過濾停用詞
  2. 創建特征矩陣和樣本數據集

    • feature_maxtrix:shape=(samples, feature_word_nums)
    • leabel; shape = (samples, 1)
    • 詞向量的選擇:索引或word2vect,注意二者的區別
  3. 拆分數據集:訓練數據集、測試數據集和驗證數據集

  4. 選擇模型,這里選擇svm

  5. 訓練、測試、調參

3. 具體實現過程

3.1 所用到的庫

import os
import jieba
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import RandomizedSearchCV,train_test_split
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score
from scipy.stats import uniform

3.2 將郵件轉換為特征詞矩陣類

class EmailToWordFeatures:
    '''
    功能:將郵件轉換為特征詞矩陣
    整個過程包括:
    - 對郵件內容進行分詞處理
    - 去除所有非中文字符,如標點符號、英文字符、數字、網站鏈接等特殊字符
    - 過濾停用詞
    - 創建特征矩陣
    '''
    def __init__(self,stop_word_file=None,features_vocabulary=None):
        
        self.features_vocabulary = features_vocabulary
        
        self.stop_vocab_dict = {}  # 初始化停用詞
        if stop_word_file is not None:
            self.stop_vocab_dict = self._get_stop_words(stop_word_file)
    
    def text_to_feature_matrix(self,words,vocabulary=None,threshold =10):
        cv = CountVectorizer()
        if vocabulary is None:
            cv.fit(words)
        else:
            cv.fit(vocabulary)
        words_to_vect = cv.transform(words)
        words_to_matrix = pd.DataFrame(words_to_vect.toarray())  # 轉換成索引矩陣
        print(words_to_matrix.shape)

        # 進行訓練特征詞選擇,給定一個閾值,當單個詞在所有郵件中出現的次數的在閾值范圍內時及選為訓練特征詞、
        selected_features = []
        selected_features_index = []
        for key,value in cv.vocabulary_.items():
            if words_to_matrix[value].sum() >= threshold:  # 詞在每封郵件中出現的次數與閾值進行比較
                selected_features.append(key)
                selected_features_index.append(value)
        words_to_matrix.rename(columns=dict(zip(selected_features_index,selected_features)),inplace=True)
        return words_to_matrix[selected_features]

    def get_email_words(self,email_path, max_email = 600):
        '''
        由於機器配置問題,作為測試給定閾值600,及正負樣本數各位600
        '''
        self.emails = email_path
        if os.path.isdir(self.emails):
            emails = os.listdir(self.emails)
            is_dir = True
        else:
            emails = [self.emails,]
            is_dir = False
            
        count = 0
        all_email_words = []
        for email in emails:
            if count >= max_email:  # 給定讀取email數量的閾值
                break
            if is_dir:
                email_path = os.path.join(self.emails,email)
            email_words = self._email_to_words(email_path)
            all_email_words.append(' '.join(email_words))
            count += 1
        return all_email_words
        
    def _email_to_words(self, email):
        '''
        將郵件進行分詞處理,去除所有非中文和停用詞
        retrun:words_list
        '''   
        email_words = []
        with open(email, 'rb') as pf:
            for line in pf.readlines():
                line = line.strip().decode('gbk','ignore')
                if not self._check_contain_chinese(line):  # 判斷是否是中文
                    continue
                word_list = jieba.cut(line, cut_all=False)  # 進行分詞處理
                for word in word_list:
                    if word in self.stop_vocab_dict or not self._check_contain_chinese(word):
                        continue  # 判斷是否為停用詞
                    email_words.append(word)
            return email_words
      
    def _get_stop_words(self,file):
        '''
        獲取停用詞
        '''
        stop_vocab_dict = {}
        with open(file,'rb') as pf:
            for line in pf.readlines():
                line = line.decode('utf-8','ignore').strip()
                stop_vocab_dict[line] = 1
        return stop_vocab_dict
    
    def _check_contain_chinese(self,check_str):
        '''
        判斷郵件中的字符是否有中文
        '''
        for ch in check_str:
            if u'\u4e00' <= ch <= u'\u9fff':
                return True
        return False

3.3 將正負郵件數據集轉換為詞特征列表,每項為一封郵件

index_file= '.\\datasets\\trec06c\\full\\index'
stop_word_file = '.\\datasets\\trec06c\\chinese_stop_vocab.txt'
ham_file = '.\\datasets\\trec06c\\ham_data'
spam_file = '.\\datasets\\trec06c\\spam_data'
email_to_features = EmailToWordFeatures(stop_word_file=stop_word_file)
ham_words = email_to_features.get_email_words(ham_file)
spam_words = email_to_features.get_email_words(spam_file)
print('ham email numbers:',len(ham_words))
print('spam email numbers:',len(spam_words))
ham email numbers: 600
spam email numbers: 600

3.4 將所有郵件轉換為特征詞矩陣,及模型輸入數據

all_email = []
all_email.extend(ham_words)
all_email.extend(spam_words)
print('all test email numbers:',len(all_email))
words_to_matrix = email_to_features.text_to_feature_matrix(all_email)
print(words_to_matrix)
all test email numbers: 1200
(1200, 22556)
      故事  領導  回到  兒子  感情  有個  大概  民俗  出國  教育  ...  培訓網  商友會  網管  埃森哲  驅鼠器  條例  \
0      1   2   1   1   1   1   1   1   1   1  ...    0    0   0    0    0   0   
1      0   0   0   0   5   0   0   0   0   0  ...    0    0   0    0    0   0   
2      0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
3      0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
4      0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
...   ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ...  ...  ...  ..  ...  ...  ..   
1195   0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
1196   0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
1197   0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
1198   0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   
1199   0   0   0   0   0   0   0   0   0   0  ...    0    0   0    0    0   0   

      智囊  教練  含雙早  王府井  
0      0   0    0    0  
1      0   0    0    0  
2      0   0    0    0  
3      0   0    0    0  
4      0   0    0    0  
...   ..  ..  ...  ...  
1195   0   0    0    0  
1196   0   0    0    0  
1197   0   0    0    0  
1198   0   0    0    0  
1199   0   0    0    0  

[1200 rows x 3099 columns]

3.5 獲取標簽矩陣

label_matrix = np.zeros((len(all_email),1))
label_matrix[0:len(ham_words),:] = 1 

4. 使用svm模型進行訓練

# 拆分數據集
x_train,x_test,y_train,y_test = train_test_split(words_to_matrix,label_matrix,test_size=0.2,random_state=42)

# 使用LinearSVC模型進行訓練
svc = LinearSVC(loss='hinge',dual=True)
param_distributions = {'C':uniform(0,10)}
rscv_clf =RandomizedSearchCV(estimator=svc, param_distributions=param_distributions,cv=3,n_iter=200,verbose=2)
rscv_clf.fit(x_train,y_train)
print('best_params:',rscv_clf.best_params_)
Fitting 3 folds for each of 200 candidates, totalling 600 fits
[CV] C=6.119041659192192 .............................................
[CV] .............................. C=6.119041659192192, total=   0.0s
[CV] C=6.119041659192192 .............................................
[CV] .............................. C=6.119041659192192, total=   0.1s
[CV] C=6.119041659192192 .............................................
[CV] .............................. C=6.119041659192192, total=   0.1s
[CV] C=6.103402593686549 .............................................
...
...
...
[CV] .............................. C=4.395657632563425, total=   0.2s
best_params: {'C': 0.0279898379592336}
# 使用測試數據集進行測試
y_prab = rscv_clf.predict(x_test)
print('accuracy:',accuracy_score(y_prab,y_test))
accuracy: 0.9791666666666666

5. 分別選擇一封正式郵件和垃圾郵件進行

正式郵件內容如下:

  • 很久以前,我為了考人大,申請了他的ID,而現在卻不對外開放了。
    申請水木的ID,真的是不知道出於什么緣故。離開校園尋找一份校園的感覺,懷着對清華的向往,還是為了偶爾無聊工作的一些調劑……
    我討厭多愁善感,卻時常沉浸於其中,生活中的挫折,不幸,讓我知道自己得堅強……
    可每天的灰色心情卻揮之不去,我可以把自己的心事埋於深處,笑着對待我身邊的每一個人,告訴我樂觀。身邊最親的人,也許知道或不知道我的脆弱和恐懼。而唯一知道的人,告訴我“希望你堅不可摧”。
    回想多年前,為“在靠近的地方住下,能掩耳不聽煩世喧囂,要一份干凈的自由自在”而感動,那,是否是對今天的預見,無知是快樂的,而不知道責任也是快樂的。我可以逃避一時,卻始終要面對。

垃圾郵件如下:

  • 這是一封善意的郵件,如給你造成不便,請隨手刪除.SOHO建站代理網誠聘兼職網站代理 
    一、職業要求:
    1、有上網條件(在家中、辦公室、網吧等地);
    2、每天能有1-2小時上網時間;
    3、有網絡應用的基礎(會上論壇發貼子、發電子郵件,
    與客戶QQ溝通等)。
    二、工作任務:
    您報名加入我公司后,公司將分配給您一個屬
    於自己的冠名為SOHO致富聯盟的網站,作為站長,您的任
    務就是利用各種方法宣傳這個網站,讓客戶從你的網站上
    購買更多的商品,並接受你的建議,也同意加盟SOHO建站
    代理網網站的兼職代理,這樣你便擁有滾滾不斷的財源。
    三、工資待遇:3000元以上/月。業績累積,收入直線上升.
def email_to_predict_matrix(words,features):
    cv = CountVectorizer()
    words_to_vect = cv.fit_transform(words)
    words_to_marix = pd.DataFrame(words_to_vect.toarray())
    vocabulary = cv.vocabulary_
    
    words_numbers_list = [] # 特征詞出現的次數列表
    for feature in features:
        if feature in cv.vocabulary_.keys():
            words_numbers_list.append(words_to_marix[vocabulary[feature]][0])
        else:
            words_numbers_list.append(0)
    words_numbers_matrix = pd.DataFrame([words_numbers_list],columns = features)
    return words_numbers_matrix
valid_ham_email = '.\\datasets\\trec06c\\valid_ham_email'
valid_spam_email = '.\\datasets\\trec06c\\valid_spam_email'

email_to_features_valid = EmailToWordFeatures(stop_word_file=stop_word_file)
valid_ham_email_words = email_to_features_valid.get_email_words(valid_ham_email)
valid_spam_email_words = email_to_features_valid.get_email_words(valid_spam_email)

valid_ham_words_maxtrix = email_to_predict_matrix(valid_ham_email_words,words_to_matrix.columns)
valid_spam_words_maxtrix = email_to_predict_matrix(valid_spam_email_words,words_to_matrix.columns)
print('測試正式郵件----------')
print('預測結果:',rscv_clf.predict(valid_ham_words_maxtrix))
測試正式郵件----------
預測結果: [1.]
print('測試垃圾郵件----------')
print('預測結果:',rscv_clf.predict(valid_spam_words_maxtrix))
測試垃圾郵件----------
預測結果: [0.]

6.1 改進計划

  1. 將特征詞矩陣改word2vect

  2. 使用mxnet神經網絡模型進行訓練

6.2 數據集整理部分的代碼

# 將正樣本和負樣本分別放在了ham_data和spam_data文件夾中
index_file= '.\\datasets\\trec06c\\full\\index'
data_file = '.\\datasets\\trec06c\\data'

ham_file_list = []
spam_file_list = []
# 讀index文件
with open(index_file,'r') as pf:
    for line in pf.readlines():
        content = line.strip().split('..')
        label,path = content
        path = path.replace('/', '\\')
        if label == 'spam ':
            spam_file_list.append(path)
        else:
            ham_file_list.append(path)
            
import os
import shutil
root = '.\\datasets\\trec06c\\'
new_ham_root = '.\\datasets\\trec06c\\ham_data'
new_spam_root = '.\\datasets\\trec06c\\spam_data'
def copy_file(filelist,new_file_path): 
    for file in filelist:
        file_name = file.split('\\')
        path = root + file
        if not os.path.exists(new_file_path):
            os.makedirs(new_file_path)
        shutil.copyfile(path, new_file_path+'\\' + file_name[-2]+ '_' + file_name[-1])


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM