2006 TREC Public Spam Corpus(中文),大约有6万多封邮件,垃圾邮件42854封,正常邮件21766封。
1.首先将正常邮件与垃圾邮件分别放在不同的一个文件夹下(因为下载下来的数据集是乱的)。
2.接下来就是一系列的数据处理。分词,去除停用词,去除英文,去除标点符号,去除一些不认识的字符。
我们可以利用scikit-learn中的CountVectorizer直接实现词袋模型。词袋模型主要就是统计出各个文本中所有的词语出现的次数,然后将这些次数转换为矩阵的表示形式,这些东西都可以在CountVectorizer中实现。在CountVectorizer中我们可以选择作为特征词的数目。在这块,我们一般选择特征词的时候都是去看词出现的次数,如果某个词出现了20次那我们就把他作为特征词,或者,我们直接利用CountVectorizer中的max_features去选择特征词的数量。具体参数说明这里不在赘述。
词袋模型主要的缺点就是产生的文本向量有时候会过于稀疏,即某个文本向量中的0比较多。还有一个问题就是,若有一句话中最重要的那个词的数量过于少,那么相应的它的次数就很少,在词袋模型中,对于理解这句话的重要意思就产生了分歧。那么如何解决这个问题呢?接下来我们看TF-IDF。
TF-IDF(Term Frequency–Inverse Document Frequency)是一种考虑单词在文本中的重要的一种修正方法。它主要解决的问题就是对于某个词在句子中的理解,它不仅考虑某个词在所有文本中出现的次数,而且还考虑了某个词在所有文档中出现权重。在所有文本中出现次数越多,那么在所有文档中给予的权重就越小。具体的公式在CSDN里面一搜就有,这里不在赘述。
我们可以直接利用scikit-learn中TfidfVectorizer来实现,也可以将CountVectorizer与TfidfTransformer结合起来实现。建议用CountVectorizer与TfidfTransformer结合起来,因为在CountVectorizer中你可以首先将出现次数大于20的作为特征词,这样选择的特征会更好一点,然后再利用TfidfTransformer转化为TF-IDF。
我们还可以将分词之后的词汇表中的每个词都转化为词向量,然后将词向量拼接起来形成一个文本的文本向量。这里我们可以采用Text-CNN(卷积神经网络)来进行拼接,之后进行文本分类。
我们首先是要将所有文本分词,并保存在一个列表中,列表中的一个元素是一个文本的词汇,这时候我们会得到词汇表。接下来我们采用keras中的Tokenizer进行处理,首先利用fit_on_texts生成一个词典,这个词典是根据所有词汇的词频大小排进行排序的,词频越大,编号越小。接下里我们利用texts_to_sequences将词汇表中的所有词语转换为它们相应的编号,我们也可以在Tokenizer中通过num_words设置编号的上限。然后我们需要将每个文本的向量维度补成相同的长度,可以利用pad_sequences。
最后我们利用keras中的Embedding层,将所有词语变为词向量,并输入到CNN中。那么Embedding层是如何将词语变为词向量的?这是我接下来要回到的一个问题。
Embedding层中有一个参数叫input_dim,这个参数控制了one-hot的维度。比如你的input_dim = 10,那么从0-9这些数字变为one-hot形式的维度就是10维,而后Embedding层中会随机初始化一个权重矩阵,将这个one-hot形式的向量与这个权重矩阵相乘就会得到你想要的嵌入维度的词向量。(大概就是这么一个过程,其中的权重矩阵生成的原理类似于skip-gram,这个我也没有细看,只是在一篇文章中看到过这个说法,之后会细看的)
除了利用Embedding层将词语转化为词向量之外,我们还可以自己生成一个词向量,可以利用word2vec产生所有词语的词向量,然后自己通过这些词向量组合一起作为一个新的权重矩阵输入到Embedding层中,这样的效果比随机初始化一个权重的效果要好。
这里我选择了SVM,逻辑回归,朴素贝叶斯和CNN。这些模型的原理我希望可以自己去理解,都是些很经典的算法了。我们可以在scikit-learn中去选择我们自己想要的模型,而CNN我采用的是Keras,简单直观好用。
// 以下是我用的所有的库
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 import linear_model //逻辑回归
from sklearn.naive_bayes import MultinomialNB //朴素贝叶斯
from sklearn.svm import LinearSVC //SVM
from sklearn.metrics import accuracy_score //计算准确度
//以下都是关于keras搭建CNN所需要的,我用的tf是1.11.0 keras是2.2.4
import keras
from keras.layers import Dense, Flatten
from keras.layers import Conv1D, MaxPooling1D
from keras.models import Sequential
from keras.preprocessing.text import Tokenizer
from keras.preprocessing import sequence
from keras.layers import Embedding
from keras.layers import Dense, LSTM, GlobalMaxPooling1D
from keras.layers import Dropout
from gensim.models import word2vec //训练词向量
import matplotlib.pyplot as plt //可视化
#使用朴素贝叶斯训练
model = MultinomialNB()
model.fit(x_train, y_train)
print('训练集的准确率为:',model.score(x_train, y_train))
# 使用LR模型进行训练
LR = linear_model.LogisticRegression()
param_distributions = {'C': uniform(0, 10)}
model = RandomizedSearchCV(estimator=LR, param_distributions=param_distributions, cv=6, n_iter=500, verbose=2) #这个东西可以自己去选择一个合适的正则化值
model.fit(x_train, y_train)
print('best_params:', model.best_params_)
# 使用LinearSVC模型进行训练
svc = LinearSVC(loss='hinge', dual=True)
param_distributions = {'C': uniform(0, 10)}
model = RandomizedSearchCV(estimator=svc, param_distributions=param_distributions, cv=6, n_iter=500, verbose=2)
model.fit(x_train, y_train)
print('best_params:', model.best_params_)
//以下是模型保存的方法,保存完之后就可以去测试了
import os
from sklearn.externals import joblib
# 创建文件目录
dirs = 'SVM_1model'
if not os.path.exists(dirs):
os.makedirs(dirs)
# 保存模型
joblib.dump(model, dirs + '/SVM_1.pkl')
#读取SVM模型
dirs = 'SVM_1model'
SVM = joblib.load(dirs + '/SVM_1.pkl')
y_preb = SVM.predict(x_test)
print('SVM的准确率为:',accuracy_score(y_preb,y_test))
//以下是利用keras搭建CNN
model = Sequential()
model.add(Embedding(input_dim = len(new_dict)+1, output_dim = 150,weights=[embeddings_matrix],input_length = 100,trainable = False)) # 使用Embedding层将每个词编码转换为词向量 max_features 是词汇表大小,一般是len(tokenizer.word_index)+1
model.add(Conv1D(256, kernel_size = 4,padding='same',activation='relu',strides=1))
#model.add(GlobalMaxPooling1D())
model.add(MaxPooling1D(pool_size=30))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dense(256, activation='relu'))
model.add(Dense(128, activation='relu'))
model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(Dense(2, activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer='adam', metrics=['accuracy'])
model.summary()