文章目录
1 基本概念
文本相似度主要用于各种搜索引擎的类似文章的推荐,或者购物网站的类似商品推荐,点评网站/微博微信平台上的类似内容推荐
1.2 基于词袋模型的基本思路
如果两个文档/两句话的用词越相似,它们的内容就应该越相似。因此,可以从词频入手,计算它们的相似程度
- 文档向量化之后,相似度的考察就可以直接转化为计算空间中距离的问题
- 缺陷:不能考虑否定词的巨大作用,不能考虑词序的差异
传统相似度的衡量计算一般可以使用编辑距离算法、余弦值法、SimHash法、n-gram法、汉明距离法、最长公共子串法、最长公共子序列法等等。
这里主要介绍
余弦相似度
TF-IDF与余弦相似性的应用(二) 找出相似文章
- 两个向量间的夹角能够很好地反映其相似程度
- 但夹角大小使用不便,因此用夹角的余弦值作为相似度衡量指标
- 余弦值越接近1,夹角越接近0度,两个向量也就越相似
例如
图片来自:https://blog.csdn.net/miner_zhu/article/details/81566456
相似度计算:改进后的分析思路
- 语料分词、清理·原始语料分词
- 语料清理语料向量化
- 将语料转换为基于关键词的词频向量
- 为了避免文章长度的差异,长度悬殊时可以考虑使用相对词频使用TF-IDF算法,找出两篇文章的关键词。例如取前20个,或者前50个计算相似度
- 计算两个向量的余弦相似度,值越大就表示越相似
当向量表示概率分布时,其他相似度测量方式比余弦相似度可能更好
2 词条相似度:word2vec
词袋模型不考虑词条之间的相关性,因此无法用于计算词条相似度。
分布式表达会考虑词条的上下文关联,因此能够提取出词条上下文中的相关性信息,而词条之间的相似度就可以直接利用此类信息加以计算。
2.1 目前主要使用gensim实现相应的算法
word2vec模型
class gensim.models.word2vec.Word2Vec(
sentences = None : 类似list of list的格式,对于特别大的文本,尽量考虑流式处理
size = 100 : 词条向量的维度,数据量充足时,300/500的效果会更好
window = 5 : 上下文窗口大小
workers = 3 : 同时运行的线程数,多核系统可明显加速计算
其余细节参数设定:
min_count = 5 : 低频词过滤阈值,低于该词频的不纳入模型
max_vocab_size = None : 每1千万词条需要1G内存,必要时设定该参数以节约内存
sample=0.001 : 负例采样的比例设定
negative=5 : 一般为5-20,设为0时不进行负例采样
iter = 5 : 模型在语料库上的迭代次数,该参数将被取消
与神经网络模型有关的参数设定:
seed=1, alpha=0.025, min_alpha=0.0001, sg=0, hs=0
)
例
继续沿用弹幕文本语料来进行分析
import pandas as pd
import numpy as np
import os
import jieba
os.chdir(r'C:\Users\Administrator\Desktop')
# 读取csv文件获取数据并存储到列表中
df = pd.read_excel('处理好的弹幕数据.xlsx')
df_ = pd.DataFrame([w for w in df['弹幕'] if len(w)>20],columns=['较长弹幕'])
df_
def my_cut(text):
my_words = ['大江大河','小鲜肉']
for i in my_words:
jieba.add_word(i)
# 加载停用词
# 停用词词典可以根据场景自己在该文本里面添加要去除的词(比如冠词、人称、数字等特定词):
stop_words = []
with open(r"C:\\Users\\Administrator\\Desktop\\停用词.txt", 'r',encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
stop_words.append(line.strip())
# stop_words[:10]
return [w for w in jieba.cut(text) if w not in stop_words and len(w)>1]
# 分词和预处理,生成list of list格式
df_['cut'] = df_['较长弹幕'].apply(my_cut)
df_.head()
# 初始化word2vec模型和词表
from gensim.models.word2vec import Word2Vec
n_dim = 300 # 指定向量维度,大样本量时300~500较好
w2vmodel = Word2Vec(size = n_dim, min_count = 10)
w2vmodel.build_vocab(df_['cut']) # 生成词表
w2vmodel
对word2vec模型进行训练
word2vecmodel.train(
sentences : iterable of iterables格式,对于特别大量的文本,尽量考虑流式处理
total_examples = None : 句子总数,int,可直接使用model.corpus_count指定
total_words = None : 句中词条总数,int,该参数和total_examples至少要指定一个
epochs = None : 模型迭代次数,需要指定
其他带默认值的参数设定:
start_alpha=None,
end_alpha=None,
word_count=0,
queue_factor=2,
report_delay=1.0,
compute_loss=False,
callbacks=()
)
# 在评论训练集上建模(大数据集时可能会花费几分钟)
# 本例消耗内存较少
%time w2vmodel.train(df_['cut'], \
total_examples = w2vmodel.corpus_count, epochs = 10)
# 训练完毕的模型实质
print(w2vmodel.wv["宋运辉"].shape)
w2vmodel.wv["宋运辉"]
w2v模型的保存和复用
w2vmodel.save(存盘路径及文件名称)
w2vmodel.load(存盘路径及文件名称)
词向量间的相似度
w2vmodel.wv.most_similar(词条)
w2vmodel.wv.most_similar("宋运辉") # 哪些词条与它最相似
w2vmodel.wv.most_similar("东宝", topn = 20)
# 寻找对应关系
w2vmodel.wv.most_similar(positive=['宋运辉', '东海'], negative=['东宝'], topn = 5)
# 计算两个词的相似度/相关程度
# 余弦相似度
print(w2vmodel.wv.similarity("宋运辉", "东海"))
print(w2vmodel.wv.similarity("结局", "第三部"))
print(w2vmodel.wv.similarity("东宝", "书记"))
# 寻找不合群的词
w2vmodel.wv.doesnt_match("东宝 东海 宋运辉".split())
‘东宝’
3 文档相似度
3.1 基于词袋模型计算文档相似度
sklearn实现
sklearn.metrics.pairwise.pairwise_distances(
X : 用于计算距离的数组
[n_samples_a, n_samples_a] if metric == 'precomputed'
[n_samples_a, n_features] otherwise
Y = None : 用于计算距离的第二数组,当metric != 'precomputed'时可用
metric = 'euclidean' : 空间距离计算方式
scikit-learn原生支持 : ['cityblock', 'cosine', 'euclidean',
'l1', 'l2', 'manhattan'],可直接使用稀疏矩阵格式
来自scipy.spatial.distance : ['braycurtis', 'canberra',
'chebyshev', 'correlation', 'dice', 'hamming', 'jaccard',
'kulsinski', 'mahalanobis', 'matching', 'minkowski',
'rogerstanimoto', 'russellrao', 'seuclidean', 'sokalmichener',
'sokalsneath', 'sqeuclidean', 'yule'] 不支持稀疏矩阵格式
n_jobs = 1 : 用于计算的线程数,为-1时,所有CPU内核都用于计算
)
例
import pandas as pd
import numpy as np
import os
import jieba
os.chdir(r'C:\Users\Administrator\Desktop')
# 读取csv文件获取数据并存储到列表中
df = pd.read_excel('处理好的弹幕数据.xlsx')
df_ = pd.DataFrame([w for w in df['弹幕'] if len(w)>20],columns=['较长弹幕'])
df_
# 分词并去除停用词和短词
def my_cut(text):
my_words = ['大江大河','小鲜肉']
for i in my_words:
jieba.add_word(i)
# 加载停用词
stop_words = []
with open(r"C:\\Users\\Administrator\\Desktop\\停用词.txt", 'r',encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
stop_words.append(line.strip())
# stop_words[:10]
return [w for w in jieba.cut(text) if w not in stop_words and len(w)>1]
cut = [ " ".join(my_cut(w)) for w in df_['较长弹幕']]
cut
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
resmtx = countvec.fit_transform(cut)
resmtx
from sklearn.metrics.pairwise import pairwise_distances
pairwise_distances(resmtx, metric = 'cosine') # 余弦相似度
pairwise_distances(resmtx) # 默认值为euclidean
# 使用TF-IDF矩阵进行相似度计算
# 计算TF-IDF矩阵
from sklearn.feature_extraction.text import TfidfTransformer
vectorizer = CountVectorizer()
wordmtx = vectorizer.fit_transform(cut) # 将文本中的词语转换为词频矩阵
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx) #基于词频矩阵计算TF-IDF值
tfidf
pairwise_distances(tfidf[:5], metric = 'cosine')
gensim实现
from gensim import similarities
simmtx = similarities.MatrixSimilarity(corpus)
simmtx
基于LDA计算余弦相似度
# 检索和文档1最相似(所属主题相同)的文档
simmtx = similarities.MatrixSimilarity(corpus) # 使用的矩阵种类需要和拟合模型时相同
simmtx
simmtx.index[:2]
# 使用gensim的LDA拟合结果进行演示
query = df_['较长弹幕']
query_bow = dictionary.doc2bow(m_cut(query))
lda_vec = ldamodel[query_bow] # 转换为lda模型下的向量
sims = simmtx[lda_vec] # 进行矩阵内向量和所提供向量的余弦相似度查询
sims = sorted(enumerate(sims), key=lambda item: -item[1])
sims
3.2 doc2vec
word2vec用来计算词条相似度非常合适。
较短的文档如果希望计算文本相似度,可以将各自内部的word2vec向量分别进行平均,用平均后的向量作为文本向量,从而用于计算相似度。
但是对于长文档,这种平均的方式显然过于粗糙。
doc2vec是word2vec的拓展,任然是一个分布式的表达,但是不以前后词取向量,而是可以直接获得sentences/paragraphs/documents的向量表达,从而可以进一步通过计算距离来得到sentences/paragraphs/documents之间的相似性。
模型概况
- 分析目的:获得文档的一个固定长度的向量表达。
- 数据:多个文档,以及它们的标签,一般可以用标题作为标签。
- 影响模型准确率的因素:语料的大小,文档的数量,越多越高;文档的相似性,越相似越好。
import jieba
import gensim
from gensim.models import doc2vec
# 准备语料库
def m_doc(doclist):
reslist = []
for i, doc in enumerate(doclist):
reslist.append(doc2vec.TaggedDocument(jieba.lcut(doc), [i]))
return reslist
corp = m_doc(df_['较长弹幕'])
corp
# 建模
d2vmodel = gensim.models.Doc2Vec(
vector_size = 300,
window = 20,
min_count = 5)
%time d2vmodel.build_vocab(corp)
d2vmodel.wv.vocab
# 将新文本转换为相应维度空间下的向量
newvec = d2vmodel.infer_vector(jieba.lcut(df_['较长弹幕'][1]))
d2vmodel.docvecs.most_similar([newvec], topn = 10)
4 文档聚类
文本聚类是将一个个文档由原有的自然语言文字信息转化成数学信息,以高维空间点的形式展现出来,通过计算哪些点距离比较近,从而将那些点聚成一个簇,簇的中心叫做簇心。一个好的聚类要保证簇内点的距离尽量的近,但簇与簇之间的点要尽量的远。
在得到文档相似度的计算结果后,文档聚类问题在本质上已经和普通的聚类分析没有区别。
注意:最常用的Kmeans使用的是平方欧氏距离,这在文本聚类中很可能无法得到最佳结果。
算法的速度和效果同样重要。
使用 Kmeans 进行聚类
import pandas as pd
import numpy as np
import os
import jieba
os.chdir(r'C:\Users\Administrator\Desktop')
# 读取csv文件获取数据并存储到列表中
df = pd.read_excel('处理好的弹幕数据.xlsx')
df_ = pd.DataFrame([w for w in df['弹幕'] if len(w)>20],columns=['较长弹幕'])
df_
# 分词并去除停用词和短词
def my_cut(text):
my_words = ['大江大河','小鲜肉']
for i in my_words:
jieba.add_word(i)
# 加载停用词
stop_words = []
with open(r"C:\\Users\\Administrator\\Desktop\\停用词.txt", 'r',encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
stop_words.append(line.strip())
# stop_words[:10]
return [w for w in jieba.cut(text) if w not in stop_words and len(w)>1]
cut = df_['较长弹幕'].apply(lambda x: " ".join(my_cut(x)))
cut
# 计算TF-IDF矩阵
from sklearn.feature_extraction.text import TfidfTransformer
vectorizer = CountVectorizer()
wordmtx = vectorizer.fit_transform(cut) # 将文本中的词语转换为词频矩阵
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx) #基于词频矩阵计算TF-IDF值
tfidf
# 进行聚类分析
from sklearn.cluster import KMeans
clf = KMeans(n_clusters = 5) # 聚为5类
s = clf.fit(tfidf)
print(s)
clf.cluster_centers_
clf.cluster_centers_.shape
(5, 1721)
clf.labels_
df_['clsres'] = clf.labels_
df_['cut'] = cut
df_
df_.sort_values('clsres').clsres
df_group = df_.groupby('clsres')
df_cls = df_group.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
df_cls
# 列出关键词以刻画类别特征
import jieba.analyse as ana
ana.set_stop_words('停用词.txt')
for item in df_cls['cut']:
print(ana.extract_tags(item, topK = 10))