文章主要內(nèi)容如下:
- 數(shù)據(jù)集介紹
- 數(shù)據(jù)預(yù)處理
- 特征提取
- 訓(xùn)練分類器
- 實(shí)驗(yàn)結(jié)果
- 總結(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)簽琴许。
2. 數(shù)據(jù)預(yù)處理
這一步將分別提取郵件樣本和樣本標(biāo)簽到一個(gè)單獨(dú)文件中益兄,順便去掉郵件的非中文字符箭券,將郵件分好詞。
郵件大致內(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)牵祟。公式如下:
在所有文檔中抖格,一個(gè)詞的 IDF 是一樣的雹拄,TF 是不一樣的。在一個(gè)文檔中坪哄,一個(gè)詞的 TF 和 IDF 越高势篡,說明該詞在該文檔中出現(xiàn)得多禁悠,在其他文檔中出現(xiàn)得少。因此粱坤,該詞對這個(gè)文檔的重要性較高瓷产,可以用來區(qū)分這個(gè)文檔。
假設(shè)文檔中出現(xiàn)的所有詞的集合為詞典 ,集合大小為 疤估,則該文檔的特征表示是一個(gè) 維向量,向量的每一個(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
這里返回的 是一個(gè) 維的樣本矩陣磷雇,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í)的肾砂。