中文垃圾郵件分類(1)

文章主要內(nèi)容如下:

  1. 數(shù)據(jù)集介紹
  2. 數(shù)據(jù)預(yù)處理
  3. 特征提取
  4. 訓(xùn)練分類器
  5. 實(shí)驗(yàn)結(jié)果
  6. 總結(jié)

1. 數(shù)據(jù)集介紹

使用中文郵件數(shù)據(jù)集:trec06c。
數(shù)據(jù)集下載地址:https://plg.uwaterloo.ca/~gvcormac/treccorpus06/

下載的數(shù)據(jù)集壓縮包里有“data” 文件夾郑临,“full” 文件夾和 “delay” 文件夾屑宠〉浞睿“data” 文件夾里面包含多個(gè)二級文件夾丧叽,二級文件夾里面才是垃圾郵件文本,一個(gè)文本代表一份郵件假瞬。“full” 文件夾里有一個(gè) index 文件剪芥,該文件記錄的是各郵件文本的標(biāo)簽琴许。

data 文件夾
index 文件


2. 數(shù)據(jù)預(yù)處理

這一步將分別提取郵件樣本和樣本標(biāo)簽到一個(gè)單獨(dú)文件中益兄,順便去掉郵件的非中文字符箭券,將郵件分好詞。
郵件大致內(nèi)容如下圖:

郵件內(nèi)容示例

每一個(gè)郵件樣本蛔六,除了郵件文本外古今,還包含其他信息滔以,如發(fā)件人郵箱、收件人郵箱等抵碟。因?yàn)槲沂窍氚牙]件分類簡單地作為一個(gè)文本分類任務(wù)來解決坏匪,所以這里就忽略了這些信息适滓。
用遞歸的方法讀取所有目錄里的郵件樣本,用 jieba 分好詞后寫入到一個(gè)文本中罚屋,一行文本代表一個(gè)郵件樣本:

import re
import jieba
import codecs
import os 
# 去掉非中文字符
def clean_str(string):
    string = re.sub(r"[^\u4e00-\u9fff]", " ", string)
    string = re.sub(r"\s{2,}", " ", string)
    return string.strip()

def get_data_in_a_file(original_path, save_path='all_email.txt'):
    files = os.listdir(original_path)
    for file in files:
        if os.path.isdir(original_path + '/' + file):
                get_data_in_a_file(original_path + '/' + file, save_path=save_path)
        else:
            email = ''
            # 注意要用 'ignore'嗅绸,不然會報(bào)錯(cuò)
            f = codecs.open(original_path + '/' + file, 'r', 'gbk', errors='ignore')
            # lines = f.readlines()
            for line in f:
                line = clean_str(line)
                email += line
            f.close()
            """
            發(fā)現(xiàn)在遞歸過程中使用 'a' 模式一個(gè)個(gè)寫入文件比 在遞歸完后一次性用 'w' 模式寫入文件快很多
            """
            f = open(save_path, 'a', encoding='utf8')
            email = [word for word in jieba.cut(email) if word.strip() != '']
            f.write(' '.join(email) + '\n')

print('Storing emails in a file ...')
get_data_in_a_file('data', save_path='all_email.txt')
print('Store emails finished !')

然后將樣本標(biāo)簽寫入單獨(dú)的文件中鱼鸠,0 代表垃圾郵件,1 代表非垃圾郵件愉昆。代碼如下:

def get_label_in_a_file(original_path, save_path='all_email.txt'):
    f = open(original_path, 'r')
    label_list = []
    for line in f:
        # spam
        if line[0] == 's':
            label_list.append('0')
        # ham
        elif line[0] == 'h':
            label_list.append('1')

    f = open(save_path, 'w', encoding='utf8')
    f.write('\n'.join(label_list))
    f.close()

print('Storing labels in a file ...')
get_label_in_a_file('index', save_path='label.txt')
print('Store labels finished !')


3. 特征提取

將文本型數(shù)據(jù)轉(zhuǎn)化為數(shù)值型數(shù)據(jù)撼唾,本文使用的是 TF-IDF 方法倒谷。

TF-IDF 是詞頻-逆向文檔頻率(Term-Frequency,Inverse Document Frequency)牵祟。公式如下:
TF = \frac{某個(gè)詞在某文檔中的出現(xiàn)次數(shù)}{該文檔總詞數(shù)}
IDF = log(\frac{文檔總數(shù)}{包含該詞的文檔數(shù) + 1})
TF-IDF = TF \times IDF
在所有文檔中抖格,一個(gè)詞的 IDF 是一樣的雹拄,TF 是不一樣的。在一個(gè)文檔中坪哄,一個(gè)詞的 TF 和 IDF 越高势篡,說明該詞在該文檔中出現(xiàn)得多禁悠,在其他文檔中出現(xiàn)得少。因此粱坤,該詞對這個(gè)文檔的重要性較高瓷产,可以用來區(qū)分這個(gè)文檔。
假設(shè)文檔中出現(xiàn)的所有詞的集合為詞典 D,集合大小為 d疤估,則該文檔的特征表示是一個(gè) d 維向量,向量的每一個(gè)元素為對應(yīng)詞在該文檔的 TF-IDF 值钞瀑。用 TF-IDF 提取文本特征的代碼如下:

import jieba
from sklearn.feature_extraction.text import TfidfVectorizer

def tokenizer_jieba(line):
    # 結(jié)巴分詞
    return [li for li in jieba.cut(line) if li.strip() != '']

def tokenizer_space(line):
    # 按空格分詞
    return [li for li in line.split() if li.strip() != '']

def get_data_tf_idf(email_file_name):
    # 郵件樣本已經(jīng)分好了詞雕什,詞之間用空格隔開显晶,所以 tokenizer=tokenizer_space
    vectoring = TfidfVectorizer(input='content', tokenizer=tokenizer_space, analyzer='word')
    content = open(email_file_name, 'r', encoding='utf8').readlines()
    x = vectoring.fit_transform(content)
    return x, vectoring

這里返回的 x 是一個(gè) n \times d 維的樣本矩陣磷雇,vectoring 可用來將文本轉(zhuǎn)化為 TF-IDF 表示。


4. 訓(xùn)練分類器

python sklearn 中有許多分類器螟蒸,直接調(diào)包就行了崩掘。代碼如下:

from sklearn.linear_model import LogisticRegression
from sklearn import svm, ensemble, naive_bayes
from sklearn.model_selection import train_test_split
from sklearn import metrics
import numpy as np

if __name__ == "__main__":
    np.random.seed(1)
    email_file_name = 'all_email.txt'
    label_file_name = 'label.txt'
    x, vectoring = get_data_tf_idf(email_file_name)
    y = get_label_list(label_file_name)

    # print('x.shape : ', x.shape)
    # print('y.shape : ', y.shape)
    
    # 隨機(jī)打亂所有樣本
    index = np.arange(len(y))  
    np.random.shuffle(index)
    x = x[index]
    y = y[index]

    # 劃分訓(xùn)練集和測試集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

    clf = svm.LinearSVC()
    # clf = LogisticRegression()
    # clf = ensemble.RandomForestClassifier()
    clf.fit(x_train, y_train)
    y_pred = clf.predict(x_test)
    print('classification_report\n', metrics.classification_report(y_test, y_pred, digits=4))
    print('Accuracy:', metrics.accuracy_score(y_test, y_pred))


5. 實(shí)驗(yàn)結(jié)果

Algorithm Accuracy Precision Recall f1 score
SVM 99.30% 99.30% 99.30% 99.29%
Random Forest 98.94% 98.94% 98.94% 98.94%
Logistics Regression 98.74% 98.74% 98.74% 98.74%

可以看到诵原,幾個(gè)算法的分類結(jié)果都很不錯(cuò)枉疼。不過也說明了 trec06c 數(shù)據(jù)集挑戰(zhàn)性不高,哈哈惹资。

總結(jié)

這一次實(shí)驗(yàn)大多數(shù)代碼都是調(diào)包的航闺。當(dāng)然,我們也不能只會調(diào)包侮措,應(yīng)該要了解背后的算法的原理分扎。甚至有時(shí)間的話胧洒,得看看 sklearn 一些包的源碼墨状,應(yīng)該挺值得借鑒學(xué)習(xí)的肾砂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末镐确,一起剝皮案震驚了整個(gè)濱河市饼煞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌派哲,老刑警劉巖臼氨,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芭届,居然都是意外死亡储矩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門褂乍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來持隧,“玉大人,你說我怎么就攤上這事逃片÷挪Γ” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵呀狼,是天一觀的道長。 經(jīng)常有香客問我损离,道長哥艇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任僻澎,我火速辦了婚禮貌踏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窟勃。我一直安慰自己祖乳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布秉氧。 她就那樣靜靜地躺著眷昆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隙赁,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天垦藏,我揣著相機(jī)與錄音,去河邊找鬼伞访。 笑死,一個(gè)胖子當(dāng)著我的面吹牛轰驳,可吹牛的內(nèi)容都是我干的厚掷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼级解,長吁一口氣:“原來是場噩夢啊……” “哼冒黑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起勤哗,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤抡爹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后芒划,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冬竟,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年民逼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泵殴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拼苍,死狀恐怖笑诅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疮鲫,我是刑警寧澤吆你,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站俊犯,受9級特大地震影響妇多,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘫析,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一砌梆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贬循,春花似錦咸包、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春坟比,著一層夾襖步出監(jiān)牢的瞬間芦鳍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工葛账, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柠衅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓籍琳,卻偏偏與公主長得像菲宴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子趋急,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容