基于中文垃圾短信数据集的经典文本分类算法实现

垃圾短信的泛滥给人们的日常生活带来了严重干扰,其中诈骗短信更是威胁到人们的信息与财产安全。因此,研究如何构建一种自动拦截过滤垃圾短信的机制有较强的实际应用价值。本文基于中文垃圾短信数据集,分别对比了朴素贝叶斯、逻辑回归、随机森林、SVM、LSTM、BiLSTM、BERT七种文本分类算法的垃圾短信分类效果。

1. 数据集设置与分析

统计发现,给定数据集包含正常短信679,365条,垃圾短信75,478条,垃圾短信数量约占短信总数的10%。将数据集按7:3的比例随机拆分为训练集与测试集。训练集与测试集的数据分布如下表所示:

类别训练集测试集
正常短信(正类)475,560203,805
垃圾短信(负类)52,83022,648
总计528,390226,453

另外,绘制训练集中正常短信与垃圾短信的词云图,可以对正常短信与垃圾短信的文本特征有较为直观的认识。从正常短信出现频率最高的前500词中随机选取的200个词的词云图如下图所示:

正常短信的词云图

从垃圾短信出现频率最高的前500词中随机选取的200个词的词云图如下图所示:

垃圾短信的词云图

可以发现:正常短信和垃圾短信在频繁词项上的区别是比较明显的。正常短信多与人们的日常生活相关,包含个人情感(如:“哈哈哈”、“宝宝”)、时事新闻(如:“记者”、“发布”)、衣食住行(如:“飞机”、“医疗”)等。而垃圾短信多与广告营销相关,包含促销力度(如“元起”、“钜”、“超值”、“最低”)、时间紧迫性(如:“赶紧”、“机会”)、促销手段(如:“抽奖”、“话费”)、时令节日(如:“妇女节”、“三月”)等。

2. 算法实现

基于上述数据集,本文从传统的机器学习方法中选择了朴素贝叶斯、逻辑回归、随机森林、SVM分类模型,从深度学习方法中选择了LSTM、BiLSTM以及预训练模型BERT进行对比实验。七种文本分类算法的优缺点总结如下表所示:

算法优点缺点
朴素贝叶斯有着坚实的数学理论基础;实现简单;学习与预测的效率都较高。实际往往不能满足特征条件独立性,在特征之间的相关性较大时分类效果不好;预设的先验概率分布的影响分类效果;在类别不平衡的数据上表现不佳。
逻辑回归实现简单;训练速度快。对于非线性的样本数据难以建模拟合;在特征空间很大时,性能不好;临界值不易确定,容易欠拟合。
随机森林训练可以高度并行化,在大数据集上训练速度有优势;能够处理高维度数据;能给出各个特征属性对输出的重要性评分。在噪声较大的情况下容易发生过拟合。
SVM可以处理线性与非线性的数据;具有较良好的泛化推广能力。参数调节与核函数选择较多地依赖于经验,具有一定的随意性。
LSTM结合词序信息。只能结合正向的词序信息。
BiLSTM结合上下文信息。模型收敛需要较长的训练时间。
BERT捕捉上下文信息的能力更强。预训练的[MASK]标记造成预训练与微调阶段的不匹配,影响模型效果;模型收敛需要更多时间。

下面依次介绍各文本分类算法的实现细节。

2.1 朴素贝叶斯

首先使用结巴分词工具将短信文本分词,去除停用词;然后抽取unigram和bigram特征,使用TF-IDF编码将分词后的短信文本向量化;最后训练朴素贝叶斯分类器。模型使用scikit-learn中的MultinomialNB,参数使用默认参数。其中,假设特征的先验概率分布为多项式分布,采用拉普拉斯平滑,所有的样本类别输出都有相同的类别先验概率。

代码如下:

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
import jieba
import re
from time import time
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import classification_report
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

#读取停用词列表
def stopwordslist(filepath):  
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]  
    return stopwords  

if __name__ == '__main__':
    #读取训练集数据
    print("Loading train dataset ...")
    t = time()
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #读取测试集数据
    print("Loading test dataset ...")
    t = time()
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Total number of labeled documents(train): %d ." % len(train_data))
    print("Total number of labeled documents(test): %d ." % len(test_data))
 
    X_train = train_data['text']
    X_test = test_data['text']

    y_train  = train_data['labels']
    y_test = test_data['labels']
    
    #计算训练集中每个类别的标注数量
    d = {'labels':train_data['labels'].value_counts().index, 'count': train_data['labels'].value_counts()}
    df_label = pd.DataFrame(data=d).reset_index(drop=True)
    print(df_label)
    
    #加载停用词
    print("Loading stopwords ...")
    t = time()
    stopwords = stopwordslist("stopwords.txt")
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #分词,并过滤停用词
    print("Starting word segmentation on train dataset...")
    t = time()
    X_train = X_train.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Starting word segmentation on test dataset...")
    t = time()
    X_test = X_test.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    #生成TF-IDF词向量
    print("Vectorizing train dataset...")
    t = time()
    tfidf = TfidfVectorizer(norm='l2', ngram_range=(1, 2))
    X_train = tfidf.fit_transform(X_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Vectorizing test dataset...")
    t = time()
    X_test = tfidf.transform(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    print(X_train.shape)
    print(X_test.shape)
    print('-----------------------------')
    print(X_train)
    print('-----------------------------')
    print(X_test)

    #训练模型
    print("Training model...")
    t = time()
    model = MultinomialNB()
    model.fit(X_train, y_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Predicting test dataset...")
    t = time()
    y_pred = model.predict(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))

    #生成混淆矩阵
    conf_mat = confusion_matrix(y_test, y_pred)
    print(conf_mat)

    print('accuracy %s' % accuracy_score(y_pred, y_test))
    print(classification_report(y_test, y_pred, digits=4))

2.2 逻辑回归

文本向量化方式与朴素贝叶斯相同。模型使用scikit-learn中的LogisticRegression,参数使用默认参数。其中,惩罚系数设置为1,正则化参数使用L2正则化,终止迭代的阈值为0.0001。

代码如下:

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
import jieba
import re
from time import time
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import classification_report
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

#读取停用词列表
def stopwordslist(filepath):  
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]  
    return stopwords  

if __name__ == '__main__':
    #读取训练集数据
    print("Loading train dataset ...")
    t = time()
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #读取测试集数据
    print("Loading test dataset ...")
    t = time()
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Total number of labeled documents(train): %d ." % len(train_data))
    print("Total number of labeled documents(test): %d ." % len(test_data))
 
    X_train = train_data['text']
    X_test = test_data['text']

    y_train  = train_data['labels']
    y_test = test_data['labels']
    
    #计算训练集中每个类别的标注数量
    d = {'labels':train_data['labels'].value_counts().index, 'count': train_data['labels'].value_counts()}
    df_label = pd.DataFrame(data=d).reset_index(drop=True)
    print(df_label)
    
    #加载停用词
    print("Loading stopwords ...")
    t = time()
    stopwords = stopwordslist("stopwords.txt")
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #分词,并过滤停用词
    print("Starting word segmentation on train dataset...")
    t = time()
    X_train = X_train.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Starting word segmentation on test dataset...")
    t = time()
    X_test = X_test.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    #生成TF-IDF词向量
    print("Vectorizing train dataset...")
    t = time()
    tfidf = TfidfVectorizer(norm='l2', ngram_range=(1, 2))
    X_train = tfidf.fit_transform(X_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Vectorizing test dataset...")
    t = time()
    X_test = tfidf.transform(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    print(X_train.shape)
    print(X_test.shape)
    print('-----------------------------')
    print(X_train)
    print('-----------------------------')
    print(X_test)

    #训练模型
    print("Training model...")
    t = time()
    model = LogisticRegression(random_state=0)
    model.fit(X_train, y_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Predicting test dataset...")
    t = time()
    y_pred = model.predict(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))

    #生成混淆矩阵
    conf_mat = confusion_matrix(y_test, y_pred)
    print(conf_mat)

    print('accuracy %s' % accuracy_score(y_pred, y_test))
    print(classification_report(y_test, y_pred, digits=4))

2.3 随机森林

文本向量化方式与朴素贝叶斯相同。模型使用scikit-learn中的RandomForestClassifier,参数使用默认参数。其中,决策树的最大个数为100,不采用袋外样本来评估模型的好坏,CART树做划分时对特征的评价标准为基尼系数。

代码如下:

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
import jieba
import re
from time import time
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import classification_report
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

#读取停用词列表
def stopwordslist(filepath):  
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]  
    return stopwords  

if __name__ == '__main__':
    #读取训练集数据
    print("Loading train dataset ...")
    t = time()
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #读取测试集数据
    print("Loading test dataset ...")
    t = time()
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Total number of labeled documents(train): %d ." % len(train_data))
    print("Total number of labeled documents(test): %d ." % len(test_data))
 
    X_train = train_data['text']
    X_test = test_data['text']

    y_train  = train_data['labels']
    y_test = test_data['labels']
    
    #计算训练集中每个类别的标注数量
    d = {'labels':train_data['labels'].value_counts().index, 'count': train_data['labels'].value_counts()}
    df_label = pd.DataFrame(data=d).reset_index(drop=True)
    print(df_label)
    
    #加载停用词
    print("Loading stopwords ...")
    t = time()
    stopwords = stopwordslist("stopwords.txt")
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #分词,并过滤停用词
    print("Starting word segmentation on train dataset...")
    t = time()
    X_train = X_train.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Starting word segmentation on test dataset...")
    t = time()
    X_test = X_test.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    #生成TF-IDF词向量
    print("Vectorizing train dataset...")
    t = time()
    tfidf = TfidfVectorizer(norm='l2', ngram_range=(1, 2))
    X_train = tfidf.fit_transform(X_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Vectorizing test dataset...")
    t = time()
    X_test = tfidf.transform(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    print(X_train.shape)
    print(X_test.shape)
    print('-----------------------------')
    print(X_train)
    print('-----------------------------')
    print(X_test)

    #训练模型
    print("Training model...")
    t = time()
    model = RandomForestClassifier()
    model.fit(X_train, y_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Predicting test dataset...")
    t = time()
    y_pred = model.predict(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))

    #生成混淆矩阵
    conf_mat = confusion_matrix(y_test, y_pred)
    print(conf_mat)

    print('accuracy %s' % accuracy_score(y_pred, y_test))
    print(classification_report(y_test, y_pred, digits=4))

2.4 SVM

文本向量化方式与朴素贝叶斯相同。模型使用scikit-learn中的LinearSVC,参数使用默认参数。其中,SVM的核函数选用线性核函数,惩罚系数设置为1,正则化参数使用L2正则化,采用对偶形式优化算法,最大迭代次数为1000,终止迭代的阈值为0.0001。

代码如下:

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
import jieba
import re
from time import time
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import classification_report
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

#读取停用词列表
def stopwordslist(filepath):  
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]  
    return stopwords  

if __name__ == '__main__':
    #读取训练集数据
    print("Loading train dataset ...")
    t = time()
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #读取测试集数据
    print("Loading test dataset ...")
    t = time()
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Total number of labeled documents(train): %d ." % len(train_data))
    print("Total number of labeled documents(test): %d ." % len(test_data))
 
    X_train = train_data['text']
    X_test = test_data['text']

    y_train  = train_data['labels']
    y_test = test_data['labels']
    
    #计算训练集中每个类别的标注数量
    d = {'labels':train_data['labels'].value_counts().index, 'count': train_data['labels'].value_counts()}
    df_label = pd.DataFrame(data=d).reset_index(drop=True)
    print(df_label)
    
    #加载停用词
    print("Loading stopwords ...")
    t = time()
    stopwords = stopwordslist("stopwords.txt")
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    #分词,并过滤停用词
    print("Starting word segmentation on train dataset...")
    t = time()
    X_train = X_train.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Starting word segmentation on test dataset...")
    t = time()
    X_test = X_test.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    #生成TF-IDF词向量
    print("Vectorizing train dataset...")
    t = time()
    tfidf = TfidfVectorizer(norm='l2', ngram_range=(1, 2))
    X_train = tfidf.fit_transform(X_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Vectorizing test dataset...")
    t = time()
    X_test = tfidf.transform(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    print(X_train.shape)
    print(X_test.shape)
    print('-----------------------------')
    print(X_train)
    print('-----------------------------')
    print(X_test)

    #训练模型
    print("Training model...")
    t = time()
    model = LinearSVC()
    model.fit(X_train, y_train)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    print("Predicting test dataset...")
    t = time()
    y_pred = model.predict(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))

    #生成混淆矩阵
    conf_mat = confusion_matrix(y_test, y_pred)
    print(conf_mat)

    print('accuracy %s' % accuracy_score(y_pred, y_test))
    print(classification_report(y_test, y_pred, digits=4))

2.5 LSTM

首先使用结巴分词工具将短信文本分词,去除停用词;然后设置保留的最大词数为最频繁出现的前50,000,序列的最大长度为100,使用200维的腾讯词向量将所有的论文标题转化为词嵌入层的权重矩阵。然后对词嵌入层的输出执行SpatialDropout1D,以0.2的比例随机将1D特征映射置零。之后输入到LSTM层,LSTM层的神经元个数为300。最后通过一个全连接层,利用softmax函数输出分类。损失函数使用交叉熵损失函数,设置batch大小为64,训练10个epoch。

代码如下:

# -*- coding: utf-8 -*-

import pandas as pd
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import jieba
import re
from time import time
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import classification_report
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense, Embedding, LSTM, SpatialDropout1D
from keras.utils.np_utils import to_categorical
from keras.callbacks import EarlyStopping
from keras.layers import Dropout
from gensim.models import KeyedVectors
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

#读取停用词列表
def stopwordslist(filepath):  
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]  
    return stopwords  

if __name__ == '__main__':
    #读取训练集数据
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    #读取测试集数据
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    
    print("Total number of labeled documents(train): %d ." % len(train_data))
    print("Total number of labeled documents(test): %d ." % len(test_data))
    
    X_train = train_data['text']
    X_test = test_data['text']

    y_train  = train_data['labels']
    y_test = test_data['labels']
    
    #计算训练集中每个类别的标注数量
    d = {'labels':train_data['labels'].value_counts().index, 'count': train_data['labels'].value_counts()}
    df_label = pd.DataFrame(data=d).reset_index(drop=True)
    print(df_label)
    #加载停用词
    stopwords = stopwordslist("stopwords.txt")
    #分词,并过滤停用词
    X_train = X_train.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    X_test = X_test.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    
    # 设置最频繁使用的50000个词(在texts_to_matrix是会取前MAX_NB_WORDS,会取前MAX_NB_WORDS列)
    MAX_NB_WORDS = 50000
    # 每个标题最大的长度
    MAX_SEQUENCE_LENGTH = 100
    # 设置Embeddingceng层的维度
    EMBEDDING_DIM = 200

    tokenizer = Tokenizer(num_words=MAX_NB_WORDS, filters='!"#$%&()*+,-./:;<=>?@[\]^_`{|}~', lower=True)
    tokenizer.fit_on_texts(X_train)
    word_index = tokenizer.word_index
    print('There are %s different words.' % len(word_index))
    
    X_train = tokenizer.texts_to_sequences(X_train)
    X_test = tokenizer.texts_to_sequences(X_test)
    
    #填充X,让X的各个列的长度统一
    X_train = pad_sequences(X_train, maxlen=MAX_SEQUENCE_LENGTH)
    X_test = pad_sequences(X_test, maxlen=MAX_SEQUENCE_LENGTH)
    #多类标签的onehot展开
    y_train = pd.get_dummies(y_train).values
    y_test = pd.get_dummies(y_test).values
    
    print(X_train.shape,y_train.shape)
    print(X_test.shape,y_test.shape)
    
    #加载tencent词向量
    wv_from_text = KeyedVectors.load_word2vec_format('tencent.txt', binary=False, unicode_errors='ignore')
    embedding_matrix = np.zeros((MAX_NB_WORDS, EMBEDDING_DIM))
    for word, i in word_index.items():
        if i > MAX_NB_WORDS:
            continue
        try:
            embedding_matrix[i] = wv_from_text.wv.get_vector(word)
        except:
            continue
    del wv_from_text
    #定义模型
    print("Training model...")
    t = time()
    model = Sequential()
    model.add(Embedding(MAX_NB_WORDS, EMBEDDING_DIM, input_length=X_train.shape[1], weights = [embedding_matrix], trainable = False))
    model.add(SpatialDropout1D(0.2))
    model.add(LSTM(300, dropout=0.2, recurrent_dropout=0.2))
    model.add(Dense(2, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    print(model.summary())
    
    epochs = 10
    batch_size = 64

    history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,validation_split=0.1,
                    callbacks=[EarlyStopping(monitor='val_loss', patience=3, min_delta=0.0001)])
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    
    accr = model.evaluate(X_test,y_test)
    print('Test set\n  Loss: {:0.3f}\n  Accuracy: {:0.3f}'.format(accr[0],accr[1]))
    
    print("Predicting test dataset...")
    t = time()
    y_pred = model.predict(X_test)
    print("Done in {0} seconds\n".format(round(time() - t, 2)))
    y_pred = y_pred.argmax(axis = 1)
    y_test = y_test.argmax(axis = 1)


    #生成混淆矩阵
    conf_mat = confusion_matrix(y_test, y_pred)
    print(conf_mat)

    print('accuracy %s' % accuracy_score(y_pred, y_test))
    print(classification_report(y_test, y_pred, digits=4))

2.6 BiLSTM

与LSTM的参数设置基本一致,只是将单向的LSTM改为双向的,训练60个epoch。

代码如下:

# -*- coding: utf-8 -*-

import pandas as pd
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import jieba
import re
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.metrics import classification_report
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense, Embedding, LSTM, SpatialDropout1D, Bidirectional
from keras.utils.np_utils import to_categorical
from keras.callbacks import EarlyStopping
from keras.layers import Dropout
from gensim.models import KeyedVectors
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

#读取停用词列表
def stopwordslist(filepath):  
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]  
    return stopwords  

if __name__ == '__main__':
    #读取训练集数据
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    #读取测试集数据
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    
    print("Total number of labeled documents(train): %d ." % len(train_data))
    print("Total number of labeled documents(test): %d ." % len(test_data))
    
    X_train = train_data['text']
    X_test = test_data['text']

    y_train  = train_data['labels']
    y_test = test_data['labels']
    
    #计算训练集中每个类别的标注数量
    d = {'labels':train_data['labels'].value_counts().index, 'count': train_data['labels'].value_counts()}
    df_label = pd.DataFrame(data=d).reset_index(drop=True)
    print(df_label)
    #加载停用词
    stopwords = stopwordslist("stopwords.txt")
    #分词,并过滤停用词
    X_train = X_train.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    X_test = X_test.apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
    
    # 设置最频繁使用的50000个词(在texts_to_matrix是会取前MAX_NB_WORDS,会取前MAX_NB_WORDS列)
    MAX_NB_WORDS = 50000
    # 每个标题最大的长度
    MAX_SEQUENCE_LENGTH = 100
    # 设置Embeddingceng层的维度
    EMBEDDING_DIM = 200

    tokenizer = Tokenizer(num_words=MAX_NB_WORDS, filters='!"#$%&()*+,-./:;<=>?@[\]^_`{|}~', lower=True)
    tokenizer.fit_on_texts(X_train)
    word_index = tokenizer.word_index
    print('There are %s different words.' % len(word_index))
    
    X_train = tokenizer.texts_to_sequences(X_train)
    X_test = tokenizer.texts_to_sequences(X_test)
    
    #填充X,让X的各个列的长度统一
    X_train = pad_sequences(X_train, maxlen=MAX_SEQUENCE_LENGTH)
    X_test = pad_sequences(X_test, maxlen=MAX_SEQUENCE_LENGTH)
    #多类标签的onehot展开
    y_train = pd.get_dummies(y_train).values
    y_test = pd.get_dummies(y_test).values
    
    print(X_train.shape,y_train.shape)
    print(X_test.shape,y_test.shape)
    
    #加载tencent词向量
    wv_from_text = KeyedVectors.load_word2vec_format('tencent.txt', binary=False, unicode_errors='ignore')
    embedding_matrix = np.zeros((MAX_NB_WORDS, EMBEDDING_DIM))
    for word, i in word_index.items():
        if i > MAX_NB_WORDS:
            continue
        try:
            embedding_matrix[i] = wv_from_text.wv.get_vector(word)
        except:
            continue
    del wv_from_text
    #定义模型
    model = Sequential()
    model.add(Embedding(MAX_NB_WORDS, EMBEDDING_DIM, input_length=X_train.shape[1], weights = [embedding_matrix], trainable = False))
    model.add(SpatialDropout1D(0.2))
    model.add(Bidirectional(LSTM(300)))
    model.add(Dense(2, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    print(model.summary())
    
    epochs = 10
    batch_size = 64

    history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,validation_split=0.1,
                    callbacks=[EarlyStopping(monitor='val_loss', patience=3, min_delta=0.0001)])
                    
    accr = model.evaluate(X_test,y_test)
    print('Test set\n  Loss: {:0.3f}\n  Accuracy: {:0.3f}'.format(accr[0],accr[1]))
    
    y_pred = model.predict(X_test)
    y_pred = y_pred.argmax(axis = 1)
    y_test = y_test.argmax(axis = 1)


    #生成混淆矩阵
    conf_mat = confusion_matrix(y_test, y_pred)
    print(conf_mat)

    print('accuracy %s' % accuracy_score(y_pred, y_test))
    print(classification_report(y_test, y_pred, digits=4))

2.7 BERT

使用BERT-Base-Chinese预训练模型在训练集上进行微调,设置学习率为1e-5,序列的最大长度为128,batch大小设置为8,训练2个epoch。

代码如下:

import pandas as pd
from simpletransformers.model import TransformerModel
from sklearn.metrics import f1_score, accuracy_score

def f1_multiclass(labels, preds):
      return f1_score(labels, preds, average='micro')

if __name__ == '__main__':
    #读取训练集数据
    train_data = pd.read_csv('train.csv', names=['labels', 'text'], sep='\t')
    #读取测试集数据
    test_data = pd.read_csv('test.csv', names=['labels', 'text'], sep='\t')
    
    print("Total number of labeled papers(train): %d ." % len(train_data))
    print("Total number of labeled papers(test): %d ." % len(test_data))
    #构建模型
    #bert-base-chinese
    model = TransformerModel('bert', 'bert-base-chinese', num_labels=2, args={'learning_rate':1e-5, 'num_train_epochs': 2, 
'reprocess_input_data': True, 'overwrite_output_dir': True, 'fp16': False})
    #bert-base-multilingual 前两个参数换成: 'bert', 'bert-base-multilingual-cased'
    #roberta 前两个参数换成: 'roberta', 'roberta-base'
    #xlmroberta 前两个参数换成: 'xlmroberta', 'xlm-roberta-base'

    #模型训练
    model.train_model(train_data)
    result, model_outputs, wrong_predictions = model.eval_model(test_data, f1=f1_multiclass, acc=accuracy_score)

3. 结果对比

为定量分析算法效果,假设正常短信为正样本,数量为P(Positive);垃圾短信为负样本,数量为N(Negative);文本分类算法正确分类样本数为T(True);错误分类样本数为F(False)。因此,真正(True positive, TP)表示正常短信被正确分类的数量;假正(False positive, FP)表示垃圾短信被误认为正常短信的数量;真负(True negative, TN)表示垃圾短信被正确分类的数量;假负(False negative, FN)表示正常短信被误认为垃圾短信的数量。在此基础上,实验中使用如下五个评估指标:

(1)精确率加权平均(Precision-weighted),计算如下:
Precision-weighted = ( P r e c i s i o n P ∗ P + P r e c i s i o n N ∗ N ) / ( P + N ) =(Precision_P*P+Precision_N*N)/(P+N) =(PrecisionPP+PrecisionNN)/(P+N)
其中 P r e c i s i o n P = T P / ( T P + F P ) Precision_P=TP/(TP+FP) PrecisionP=TP/(TP+FP) P r e c i s i o n N = T N / ( T N + F N ) Precision_N=TN/(TN+FN) PrecisionN=TN/(TN+FN)

(2)召回率加权平均(Recall-weighted),计算如下:
Recall-weighted = ( R e c a l l P ∗ P + R e c a l l N ∗ N ) / ( P + N ) =(Recall_P*P+Recall_N*N)/(P+N) =(RecallPP+RecallNN)/(P+N)
其中 R e c a l l P = T P / ( T P + F N ) Recall_P=TP/(TP+FN) RecallP=TP/(TP+FN) R e c a l l N = T N / ( T N + F P ) Recall_N=TN/(TN+FP) RecallN=TN/(TN+FP)

(3)F1值加权平均(F1-score-weighted),计算如下:
F1-score-weighted = ( F 1 P ∗ P + F 1 N ∗ N ) / ( P + N ) =(F1_P*P+F1_N*N)/(P+N) =(F1PP+F1NN)/(P+N)
其中,
F 1 P = 2 ∗ P r e c i s i o n P ∗ R e c a l l P / ( P r e c i s i o n P + R e c a l l P ) F1_P=2*Precision_P*Recall_P/(Precision_P+Recall_P) F1P=2PrecisionPRecallP/(PrecisionP+RecallP)
F 1 N = 2 ∗ P r e c i s i o n N ∗ R e c a l l N / ( P r e c i s i o n N + R e c a l l N ) F1_N=2*Precision_N*Recall_N/(Precision_N+Recall_N) F1N=2PrecisionNRecallN/(PrecisionN+RecallN)

(4)假负率(False negative rate, FNR),计算如下:
FNR = F N / ( T P + F N ) =FN/(TP+FN) =FN/(TP+FN),即被预测为垃圾短信的正常短信数量/正常短信实际的数量。

(5)真负率(True negative rate, TNR),计算如下:
TNR = T N / ( T N + F P ) =TN/(TN+FP) =TN/(TN+FP),即垃圾短信的正确识别数量/垃圾短信实际的数量,亦为垃圾短信的召回率。

针对垃圾短信分类的场景,我们希望一个好的文本分类算法使得精确率加权平均、召回率加权平均、F1值加权平均、真负率要尽可能的高,即垃圾短信的正确拦截率高;同时,必须保证假负率尽可能的低,即正常短信被误认为是垃圾短信的比率低。这是因为:对于用户来说,“正常短信被误认为是垃圾短信”比“垃圾短信被误认为是正常短信”更不可容忍;对于运营商来说,宁可放过部分垃圾短信,也要保障用户的正常使用。

模型精确率加权平均召回率加权平均F1值加权平均假负率真负率
朴素贝叶斯0.97640.97610.97480.00100.7700
逻辑回归0.98860.98870.98870.00610.9414
随机森林0.98090.98080.98000.00120.8181
SVM0.99250.99240.99240.00520.9713
LSTM0.99630.99630.99630.00150.9771
BiLSTM0.99640.99640.99640.00090.9720
BERT0.99910.99910.99910.00020.9926

上表给出了七种文本分类算法的实验结果。可以发现:

第一,BERT具有最高的F1值加权平均和真负率,同时具有最低的假负率,垃圾短信的过滤效果最好。分析原因是BERT经过大规模通用语料上的预训练,对文本特征的捕捉能力更强。

第二,BiLSTM与LSTM的F1值加权平均接近,因此模型整体的分类效果接近,但二者的假负率与真负率存在差异:从假负率来看,BiLSTM的正常短信错误识别率更低;从真负率来看,LSTM的垃圾短信正确拦截率更高。

第三,SVM与逻辑回归的F1值加权平均比较接近,但相较而言,SVM的效果更好一些:SVM在精确率加权平均、召回率加权平均、F1值加权平均、假负率、真负率这五个指标上均比逻辑回归略胜一筹。分析原因可能是:SVM仅考虑支持向量,也就是和分类最相关的少数样本点;而逻辑回归考虑所有样本点,因此逻辑回归对异常值与数据分布的不平衡更敏感,分类效果受到影响。

第四,朴素贝叶斯与随机森林在F1值加权平均和真负率上表现较差。分析原因可能是:正负例数据的不平衡对二者的模型效果造成影响,模型在正常短信数据上有些过拟合。此外,朴素贝叶斯的条件独立性假设在实际中不满足,这在一定程度上影响分类效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/343853.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

哈希--73. 矩阵置零/medium 理解度A

73. 矩阵置零 1、题目2、题目分析3、复杂度最优解代码示例4、适用场景 1、题目 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,…

Overleaf(LaTeX文档在线编写平台)使用学习记录

一、LaTeX简概[1] LaTeX&#xff0c;是一种基于TEX的排版系统&#xff0c;是一种可以处理排版和渲染的标记语言。由美国计算机科学家莱斯利兰伯特在20世纪80年代初期开发&#xff0c;利用这种格式系统的处理&#xff0c;即使用户没有排版和程序设计的知识也可以充分发挥由TEX所…

CACTER邮件安全网关独家安全解决方案——保障企业邮件系统安全

随着科技的不断发展&#xff0c;网络攻击技术也在不断演变&#xff0c;尤其是在电子邮件领域&#xff0c;各种高级变种威胁层出不穷&#xff0c;比如定制化的钓鱼邮件和带有高级恶意软件的邮件等。这些威胁邮件往往能够绕过传统的安全防护措施&#xff0c;包括反垃圾邮件、反钓…

软件安全测试的重要性简析,专业安全测试报告如何申请?

在当今数字化时代&#xff0c;软件在我们的日常生活中扮演着至关重要的角色&#xff0c;但也带来了各种潜在的安全威胁。为了保障用户的信息安全和维护软件的可靠性&#xff0c;软件安全测试显得尤为重要。 软件安全测试是指通过一系列的方法和技术&#xff0c;对软件系统中的…

pikachu_csrf通关攻略

csrf&#xff08;get&#xff09; 打开pikachu靶场&#xff1a; 1. 根据提示给的账户密码进行登录 2. 打开代理拦截数据包将拦截数据发送到已打开的burp中&#xff1a; 修改数据进行发包&#xff1a; 从上面的url可见&#xff0c;修改用户信息的时候&#xff0c;是不带任何不…

性能优化(CPU优化技术)-NEON指令介绍

「发表于知乎专栏《移动端算法优化》」 本文主要介绍了 NEON 指令相关的知识&#xff0c;首先通过讲解 arm 指令集的分类&#xff0c;NEON寄存器的类型&#xff0c;树立基本概念。然后进一步梳理了 NEON 汇编以及 intrinsics 指令的格式。最后结合指令的分类&#xff0c;使用例…

如何基于 ESP32 芯片测试 WiFi 连接距离、获取连接的 AP 信号强度(RSSI)以及 WiFi吞吐测试

测试说明&#xff1a; 测试 WiFi 连接距离&#xff0c;是将 ESP32 作为 WiFi Station 模式来连接路由器&#xff0c;通过在开阔环境下进行拉距来测试。另外&#xff0c;可以通过增大 WiFi TX Power 来增大连接距离。 获取连接的 AP 信号强度&#xff0c;一般可以通过 WiFi 扫描…

机器学习_从线性回归到逻辑回归原理和实战

文章目录 介绍分类问题用线性回归阶跃函数完成分类通过 Sigmiod 函数进行转换逻辑回归的假设函数逻辑回归的损失函数用逻辑回归解决二元分类问题 介绍分类问题 机器学习两个主要应用是回归和分类问题。 逻辑回归算法的本质其实仍然是回归。这个算法也是通过调整权重w和偏置b来…

GBASE南大通用提供给.NET 应用程序访问 GBase 数据库、获取数据、管理数据的一套完整的解决方案

GBase ADO.NET&#xff08;全称是 .NET Framework Data Provider For GBase&#xff09;提 供给.NET 应用程序访问 GBase 数据库、获取数据、管理数据的一套完整的解决 方案。 GBase ADO.NET 的四个核心类及若干功能类具有以下功能&#xff1a;  建立和管理与 GBase 数据库连…

java web mvc-04-Apache Wicket

拓展阅读 Spring Web MVC-00-重学 mvc mvc-01-Model-View-Controller 概览 web mvc-03-JFinal web mvc-04-Apache Wicket web mvc-05-JSF JavaServer Faces web mvc-06-play framework intro web mvc-07-Vaadin web mvc-08-Grails 开源 The jdbc pool for java.(java …

21.云原生之ArgoCD CICD实战(部分待补充)

云原生专栏大纲 文章目录 部署项目介绍项目结构介绍GitLab CI/CDGitLab CI/CD主要特点和功能 部署测试argocd的cd过程CICD工作流准备工作github中工作流文件创建gitlab中工作流文件创建【实操待补充】GitLab CI示例 数据加密之seale sealedBitnami Sealed Secrets介绍Bitnami …

11.前端--CSS-背景属性

1.背景颜色 样式名称&#xff1a; background-color 定义元素的背景颜色 使用方式: background-color:颜色值; 其他说明&#xff1a; 元素背景颜色默认值是 transparent&#xff08;透明&#xff09;      background-color:transparent; 代码演示&#xff1a; 背景色…

盖子的c++小课堂:第二十六讲:双向链表

前言 谢谢各位粉丝的支持,望我早日突破1000粉 双向链表 干货!单链表从原理到实现——附python和C++两个版本 - 知乎单链表是链表家族中的一员,每个节点依旧由 数据域(data)和指针域(next)组成,链表的具体概念下面有介绍: 机器学习入坑者:程序员基本功——链表的基…

大数据学习之Flink算子、了解(Source)源算子(基础篇二)

Source源算子&#xff08;基础篇二&#xff09; 目录 Source源算子&#xff08;基础篇二&#xff09; 二、源算子&#xff08;source&#xff09; 1. 准备工作 2.从集合中读取数据 可以使用代码中的fromCollection()方法直接读取列表 也可以使用代码中的fromElements()方…

“探索C语言操作符的神秘世界:从入门到精通的全方位解析“

各位少年&#xff0c;我是博主那一脸阳光&#xff0c;今天来分享深度解析C语言操作符&#xff0c;C语言操作符能帮我们解决很多逻辑性的问题&#xff0c;减少很多代码量&#xff0c;就好比数学的各种符号&#xff0c;我们现在深度解剖一下他们。 前言 在追求爱情的道路上&…

Google ASPIRE框架:赋予大型语言模型(LLMs)自我评估的新动力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Vue生命周期;综合案例;工程化开发入门

Vue的生命周期 和 生命周期的四个阶段 思考&#xff1a; 什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff1a;最早可以早到什么时候&#xff1f;&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命…

【C++】C++入门(一)

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. C关键字3. 命名空间3.1 命名空间定义3.2 命名空间的使用 4. C输入&输出 1. 前言 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0…

4G物联网LED智慧路灯杆显示屏产品介绍

4GLED显示屏是一种具有4G网络连接功能的LED显示屏。它可以通过4G网络连接到互联网&#xff0c;实现远程管理和控制&#xff0c;方便进行内容更新和管理。同时&#xff0c;4GLED显示屏具有高亮度、高清晰度和高对比度的特点&#xff0c;可以提供清晰明亮的图像和视频展示效果。它…

【前端小点】Vue3中的IP输入框组件

本文章记录,如何在vue3项目开发中,使用ip输入框组件. 之前写过vue2版本的ip组件,为了更好的适应vue3,此次进行vue3代码重写 先上效果图: 禁用效果图: 主要是组件的开发,代码如下,可直接拷贝使用. 大概思路就是: 使用四个输入框拼接,然后给输入内容添加校验操作,添加光标移动,…