LSI(潜在语义索引)的python实现
本篇博客记录两种LSI的python实现,一种利用gensim,一种利用sklearn。
使用到的函数工具
def get_data(intro_list):
# 中文预处理
new_list = []
for intro in intro_list:
new_list.append(format_str(intro))
# 进行分词
fenci_list = []
fenci_list = fenci(new_list)
# 去除停用词
with open('./stopwords.txt', 'r', encoding='utf-8') as f:
stopwords_list = []
for line in f.readlines():
stopwords_list.append(line.strip())
clean_list = drop_stopwords(fenci_list, stopwords_list)
return clean_list
# 中文文本预处理
def is_cnen(uchar):
# 只保留汉字与英文,去掉了数字标点和特殊字符
if (uchar >= u'\u4e00' and uchar <= u'\u9fa5') or (uchar >= u'\u0041' and uchar <= u'\u005A') or (uchar >= u'\u0061' and uchar <= u'\u007A'):
return True
else:
return False
def format_str(content):
content_str = ''
for i in content:
if is_cnen(i):
content_str = content_str + i
return content_str
# 分词
def fenci(new_list):
cut_words = map(lambda s: list(jieba.cut(s)), new_list)
return list(cut_words)
# 去除停用词
def drop_stopwords(fenci_list, stopwords):
fenci_list_clean = []
for line in fenci_list:
line_clean = []
for word in line:
if word in stopwords:
continue
line_clean.append(word)
fenci_list_clean.append(line_clean)
return fenci_list_clean
def cos_sim(vector_a, vector_b):
"""
计算两个向量之间的余弦相似度
:param vector_a: 向量 a
:param vector_b: 向量 b
:return: sim
"""
vector_a = np.mat(vector_a)
vector_b = np.mat(vector_b)
num = float(vector_a * vector_b.T)
denom = np.linalg.norm(vector_a) * np.linalg.norm(vector_b)
cos = num / denom
sim = 0.5 + 0.5 * cos
return sim
gensim方法
import jieba
import gensim
from gensim import corpora
from gensim import similarities
import pandas as pd
from utils import *
# 读取数据,进行中文预处理,得到词语字典,将文档变成DT矩阵
# 读取frame中的app概要说明
game_frame = pd.read_csv("../data/game.csv")
intro_list = game_frame['概要说明'].values.tolist()
# 得到的clean_list是预处理完成的词语列表
# get_data函数对读入数据进行预处理,保留汉字与英文字母,分词,去除停用词
clean_list = get_data(intro_list)
# 下一步准备 Document-Term 矩阵
# 创建语料的词语词典,每个单独的词语都会被赋予一个索引
dictionary = corpora.Dictionary(clean_list)
# 使用上面的词典,将转换文档列表(语料)变成 DT 矩阵
doc_term_matrix = [dictionary.doc2bow(doc) for doc in clean_list]
# 创建tfidf对象
# LSI LDA 等模型通常用bow向量或是tfidf向量作为语料输入,上面的doc_term_matrix就是bow向量
tfidf = gensim.models.TfidfModel(doc_term_matrix)
corpus_tfidf = tfidf[doc_term_matrix]
# 构建LDA模型进行训练
Lda = gensim.models.ldamodel.LdaModel
ldamodel = Lda(corpus_tfidf, num_topics=3, id2word=dictionary, passes=50)
# 构建LSI模型进行训练
Lsi = gensim.models.LsiModel
lsimodel = Lsi(corpus_tfidf, id2word=dictionary, num_topics=100)
# 用待检索的文档向量初始化一个相似度计算的对象
index = similarities.MatrixSimilarity(lsimodel[corpus_tfidf])
# 保存相似度矩阵,index中每一行表示一篇文档,列表示主题,列数与前面定义的num_topics数是一致的
index.save('./sim_mat.index')
index = similarities.MatrixSimilarity.load('./sim_mat.index')
# 计算一篇文档与现有语料中所有文档的(余弦)相似度
# 这里先取语料中的第一篇文档试验一下
query_bow = dictionary.doc2bow(clean_list[0])
# tfidf向量化
query_tfidf = tfidf[query_bow]
# 用之前训练好的LSI模型将其映射到topic空间
query_vec = lsimodel[query_tfidf]
# 检查query在index中的相似度
sim = index[query_vec]
print(list(enumerate(sim)))
sklearn方法
# --*--conding:utf-8 --*--
import jieba
import jieba.analyse
import json
import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.preprocessing import Normalizer
from sklearn.pipeline import make_pipeline
from utils import *
# 读取数据
game_frame = pd.read_csv("../data/game.csv")
intro_list = game_frame['概要说明'].values.tolist()
name_list = game_frame['name'].values.tolist()
# 利用get_data函数得到预处理后的数据
clean_intro_list = get_data(intro_list)
# 把所以文档整理到一个列表中,列表中的每一项是字符串,表示一篇文档,文档中的分词之间用空格隔开
str_intro_list = []
for doc in clean_intro_list:
doc_str = ' '.join(doc)
str_intro_list.append(doc_str)
# 建立tfidf对象。这里可以添加停用词,由于前面做文本预处理的时候已经做过了,所以这里可以忽略
vec = TfidfVectorizer(ngram_range=(1, 1), use_idf=True, smooth_idf=True, sublinear_tf=True, stop_words=None)
# 将对象应用在存文档的列表上,得到带有权重的词表
keywords = vec.fit_transform(str_intro_list)
# 建立svd对象
svd = TruncatedSVD(n_components=100)
# 将svd与正则化
norm = Normalizer(copy=False)
# Pipeline里面需要一个列表,列表里元素是一个个元组,每个元组代表对数据的处理,元组的第一个参数是处理的别名,随便取,第二个参数是处理的函数。这里的第一项任务是svd,第二步正则化,使用pipeline的时候sklearn会顺序执行这些步骤,相当于将几步任务串行顺序运算。而这里的make_pipeline是简化版的Pipeline,不需要也不允许为函数命名。
# 这一步手工构建了lsi对象
lsi = make_pipeline(svd, norm)
# 将tfidf后的文档fit到lsi模型中
all_topic_vector = lsi.fit_transform(keywords)
# 把游戏名和对应的主题向量存到字典里,key是游戏名,value是主题向量
# 建立字典,用于存储向量
topic_vector = {}
for i in range(len(name_list)):
topic_vector[name_list[i]] = all_topic_vector[i]
for name in topic_vector:
sim = 0
for same_name in topic_vector:
# cos_sim 是自己定义的计算余弦相似度的函数
o_sim = cos_sim(topic_vector[name], topic_vector[same_name])
if o_sim > sim and o_sim != 1:
sim = o_sim
sim_name = same_name
print(name, sim_name)
总结
gensim的方法需要建立字典映射,将每个分好的词建立索引,根据字典,把文档变成bow向量的形式,进一步可以用tfidf,再将向量作为lsi的参数。gensim的包里都有对应的工具,可以很容易的建立字典、形成向量、建立lsi模型、生成相似度矩阵等等。
sklearn的方法需要手工搭建lsi,利用TfidfVectorize函数得到tfidf向量(而且这个函数好像是自动建立了字典映射),再构建svd降维和正则化,将向量作为参数导入进去,得到了每篇文档的主题向量。