自然语言处理--短消息二元分类LDA 模型:垃圾消息过滤器(原理实现)

LDA是一种监督学习的降维技术,也就是说它的数据集的每个样本是有类别输出的。这点和PCA不同。PCA是不考虑样本类别输出的无监督降维技术。LDA的思想可以用一句话概括,就是“投影后类内方差最小,类间方差最大”。什么意思呢? 我们要将数据在低维度上进行投影,投影后希望每一种类别数据的投影点尽可能的接近,而不同类别的数据的类别中心之间的距离尽可能的大。

下面,我们给出了 LDA 的一个简单的实现版本,模型训练有 3 个步骤:
(1)计算某个类(如垃圾短消息类)中所有 TF-IDF 向量的平均位置(质心)。
(2)计算不在该类(如非垃圾短消息类)中的所有 TF-IDF 向量的平均位置(质心)。
(3)计算上述两个质心之间的向量差(即连接这两个向量的直线)。
即要“训练”LDA 模型,只需找到两个类的质心之间的向量(直线)。LDA 是一种有监督算法,因此需要为消息添加标签。要利用该模型进行推理或预测,只需要判断新的 TF-IDF 向量是否更接近类内(垃圾类)而不是类外(非垃圾类)的质心。

# 来训练一个 LDA 模型,将短消息分为垃圾类或非垃圾类
import pandas as pd
from nlpia.data.loaders import get_data
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.tokenize.casual import casual_tokenize
from sklearn.preprocessing import MinMaxScaler
from pugnlp.stats import Confusion

# 导入垃圾短消息数据集
pd.options.display.width = 120
# 数据集中有 4837 条短消息
sms = get_data('sms-spam')  # <class 'pandas.core.frame.DataFrame'>
print(sms)
# 通过添加感叹号!标注垃圾短消息
index = ['sms{}{}'.format(i, '!'*j) for (i,j) in zip(range(len(sms)), sms.spam)]
sms = pd.DataFrame(sms.values, columns=sms.columns, index=index)
sms['spam'] = sms.spam.astype(int)
print(len(sms))
# 638 条被标注为二类标签“spam”(垃圾类)
print(sms.spam.sum())
print(sms.head(6))

# 构造TF-IDF 向量
tfidf_model = TfidfVectorizer(tokenizer=casual_tokenize)
tfidf_docs = tfidf_model.fit_transform(raw_documents=sms.text).toarray()
print("tfidf_docs.shape\n", tfidf_docs.shape)
print(sms.spam.sum())

'''
LDA 的一个简单的实现:
(1)计算某个类(如垃圾短消息类)中所有 TF-IDF 向量的平均位置(质心)。
(2)计算不在该类(如非垃圾短消息类)中的所有 TF-IDF 向量的平均位置(质心)。
(3)计算上述两个质心之间的向量差(即连接这两个向量的直线)
'''
# 计算两个类(垃圾类和非垃圾类)的质心
mask = sms.spam.astype(bool).values
# 仅返回垃圾类的行
# TF-IDF 向量是行向量,确保 numpy 使用 axis=0 独立计算每一列的平均值
spam_centroid = tfidf_docs[mask].mean(axis=0)
# 仅返回非垃圾类的行
ham_centroid = tfidf_docs[~mask].mean(axis=0)
print(spam_centroid.round(2))
print(ham_centroid.round(2))
# 用一个质心向量减去另一个质心向量从而得到分类线
# 点积计算的是每个TF-IDF向量在质心连线上的“阴影”投影
spamminess_score = tfidf_docs.dot(spam_centroid - ham_centroid)
# 非垃圾类质心将它们投影到质心的连线上时,我们可能会得到一个负的垃圾信息评分
print(spamminess_score.round(2))

# 使上述评分就像概率那样取值在 0 到 1 之间
# MinMaxScaler:归一到 [ 0,1 ]
sms['lda_score'] = MinMaxScaler().fit_transform(spamminess_score.reshape(-1,1))
sms['lda_predict'] = (sms.lda_score > .5).astype(int)
# 当将阈值设置为 50%时,前 6 条消息都被正确分类。
print(sms['spam lda_predict lda_score'.split()].round(2).head(6))
# 计算精确度
print((1. - (sms.spam - sms.lda_predict).abs().sum() / len(sms)).round(3))

# 看看训练集上的混淆矩阵:
# 给出了标注为垃圾但是根本不是垃圾的消息(假阳),
# 也给出了标注为非垃圾但是本应该标注为垃圾的消息(假阴)
# 假阳(64)、假阴(45)
'''
lda_predict     0    1
spam                  
0            4135   64
1              45  593
'''
print(Confusion(sms['spam lda_predict'.split()]))

猜你喜欢

转载自blog.csdn.net/fgg1234567890/article/details/112437536