自然语言处理--LDiA+LDA构建垃圾消息过滤器

LDiA 擅长挖掘可解释的主题,如用于摘要的主题,而且在产生对线性分类有用的主题方面 LDiA 也不差。下面我们看看这些 LDiA 主题在预测(如消息的垃圾性)时的有效性。

使用 LDiA主题向量来训练 LDA 模型:

from sklearn.feature_extraction.text import CountVectorizer
from nltk.tokenize import casual_tokenize
import numpy as np
import pandas as pd
from nlpia.data.loaders import get_data
from nltk.tokenize.casual import casual_tokenize
from sklearn.model_selection import train_test_split
from sklearn.decomposition import LatentDirichletAllocation as LDiA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.tokenize.casual import casual_tokenize

# 从 nlpia 包中的 DataFrame 加载短消息数据
pd.options.display.width = 120
sms = get_data('sms-spam')
# 向短消息的索引号后面添加一个感叹号,以使垃圾短消息更容易被发现
index = ['sms{}{}'.format(i, '!'*j) for (i,j) in zip(range(len(sms)), sms.spam)]
sms.index = index
print(sms.head(6))

# 计算词袋向量
np.random.seed(42)
counter = CountVectorizer(tokenizer=casual_tokenize)
bow_docs = pd.DataFrame(counter.fit_transform(raw_documents=sms.text).toarray(), index=index)
column_nums, terms = zip(*sorted(zip(counter.vocabulary_.values(), counter.vocabulary_.keys())))
bow_docs.columns = terms
# 看看对标记为“sms0”的第一条短消息
print(sms.loc['sms0'].text)
print( bow_docs.loc['sms0'][bow_docs.loc['sms0'] > 0].head())

ldia = LDiA(n_components=16, learning_method='batch')
ldia = ldia.fit(bow_docs)
# 将 9232 个词(词项)分配给 16 个主题(成分)
print(ldia.components_.shape)

# 看看开头的几个词,我们了解一下它们是如何分配到 16 个主题中的。
pd.set_option('display.width', 75)
columns = ['topic{}'.format(i) for i in range(ldia.n_components)]
components = pd.DataFrame(ldia.components_.T, index=terms, columns=columns)
# 感叹号(!)被分配到大多数主题中,但它其实是 topic3 中一个特别重要的部分,
# 在该主题中引号(")几乎不起作用。
print(components.round(2).head(3))
# 该主题的前十个词条似乎是在要求某人做某事或支付某事的强调指令中可能使用的词类型。
print(components.topic3.sort_values(ascending=False)[:10])

# 生成主题向量
ldia16_topic_vectors = ldia.transform(bow_docs)
ldia16_topic_vectors = pd.DataFrame(ldia16_topic_vectors, index=index, columns=columns)
# 对比于pca,svd,ldia产生的主题之间分隔得更加清晰
print(ldia16_topic_vectors.round(2).head())

# lda
X_train, X_test, y_train, y_test = train_test_split(ldia16_topic_vectors, sms.spam, test_size=0.5, random_state=271828)
lda = LDA(n_components=1)
'''
ldia_topic_vectors 矩阵的行列式接近于零,所以很可能会得到
“变量是共线的”这类警告。这种情况可能发生在小型语料库
上使用 LDiA 的场景,因为这时的主题向量中有很多 0,并且
一些消息可以被重新生成为其他消息主题的线性组合。另一
种可能的场景是,语料库中有一些具有相似(或相同)主题
混合的短消息。共线警告可能发生的一种情况是,如果文本包含一些 2-gram 或 3-gram,
其中组成它们的词只同时出现在这些 2-gram 或 3-gram 中。
'''
lda = lda.fit(X_train, y_train)
sms['ldia16_spam'] = lda.predict(ldia16_topic_vectors)
# 测试集上取得 94%的精确度
print(round(float(lda.score(X_test, y_test)), 2))

# 看看与基于 TF-IDF 向量的高维模型相比结果如何
tfidf = TfidfVectorizer(tokenizer=casual_tokenize)
tfidf_docs = tfidf.fit_transform(raw_documents=sms.text).toarray()
tfidf_docs = tfidf_docs - tfidf_docs.mean(axis=0)
X_train, X_test, y_train, y_test = train_test_split(tfidf_docs, sms.spam.values, test_size=0.5, random_state=271828)
# “假装”所有短消息中都只有一个主题
lda = LDA(n_components=1)
# 它用一个 9232维的超平面分割向量空间!
lda = lda.fit(X_train, y_train)
print(round(float(lda.score(X_train, y_train)), 3))
# TF-IDF 向量有更多的特征(超过 3000 个独立的词项)。所以很可能会遇到过拟合和弱泛化问题
print(round(float(lda.score(X_test, y_test)), 3))

# 增加主题数量,看看主题向量中0是否会减少:32 个 LDiA 主题
ldia32 = LDiA(n_components=32, learning_method='batch')
ldia32 = ldia32.fit(bow_docs)
print(ldia32.components_.shape)
ldia32_topic_vectors = ldia32.transform(bow_docs)
columns32 = ['topic{}'.format(i) for i in range(ldia32.n_components)]
ldia32_topic_vectors = pd.DataFrame(ldia32_topic_vectors, index=index, columns=columns32)
# 可以看到,这些主题甚至更加稀疏,而且能更加清晰地分隔开。因此增加或减少主题的数量并
# 不能解决或造成共线问题,这是底层数据造成的。
# 如果想摆脱这个警告,那么需要将“噪声”或元数据以人造词的方式添加到短消息中,或者需要删除那些重复的词向量。
# 如果文档中有重复出现多次的词向量或词对,那么主题的数量优化也无法解决这个问题。
print(ldia32_topic_vectors.round(2).head())

X_train, X_test, y_train, y_test =  train_test_split(ldia32_topic_vectors, sms.spam, test_size=0.5, random_state=271828)
lda = LDA(n_components=1)
lda = lda.fit(X_train, y_train)
sms['ldia32_spam'] = lda.predict(ldia32_topic_vectors)
print(X_train.shape)
print(round(float(lda.score(X_train, y_train)), 3))
# 这里 93.6%的测试结果与使用16 维 LDiA 主题向量时 94%的测试结果相当
print(round(float(lda.score(X_test, y_test)), 3))

猜你喜欢

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