一、背景及概述
对于一篇文章或者是一段文字信息,我们想要获取其中的关键信息,如果是中文,我们首先要对其进行分词的预处理,中文分词有很多开源的技术,如python就有结巴模块用来做中文分词,网上有很多博客详细讲解,这边不是我们这部分工作的重点内容,在这里就不详细讲解了。(关于python中结巴分词)通过对连续的语义的分词我们会得到由多个独立词语构成的信息,这里就需要合适的信息检索模型,来判断两个不同的文章或语句信息之间的相似程度了,常用的信息检索模型分为三类,集合论模型,代数模型以及概率模型。以代数模型为例,向量空间模型是代数模型的一种,这种模型是将文档表示成空间向量,通过计算向量之间的余弦相似度来比较两篇文档之间的相关程度,然而这种模型建立出来的空间向量维度十分高,最简单的向量空间模型中,加入词典的大小为N维,那么一篇文档的向量也是N维,M篇文章就会构成一个M*N维度的矩阵,但是在在这个矩阵中又有很多值为0的特征,非常冗余;因此可想而知,用如此高纬度的向量计算是多么麻烦。在这类问题的基础上,在信息检索的领域内,我们需要做一些特征空间的变化,来简化运算以及提高效率,并且实验也证明,通过一定的特征空间变化之后得到的效果要优于直接运算。
二、特征空间变化主要方法简介
常用的特征空间变化有,奇异值分解SVD,隐语义分析LSA,PLSA主题模型,LDA主题模型。
简单介绍一下,奇异值分解是线性代数中一种重要的矩阵分解
矩阵A可以理解为我们的M个信息段构成的M*N的矩阵,对于矩阵U是一个M*M的矩阵,这个矩阵中所有的向量是正交的,Σ是一个N*M的矩阵,而且还是个对角阵,即除了对角线上的元素都为0;V'是一个N*N的矩阵,里面的向量也都是正交的。我们可以将Σ理解成奇异值矩阵,其对角线上的每一个元素是奇异值,并且在这个矩阵中对角线上的元素是按照奇异值的大小排列的,同时,前百分之十以内的奇异值在整个运算中占的比重几乎是全部,因此我们可以用前r个奇异值和两个正交矩阵的相乘结果来近似原本的矩阵
易理解,r越接近N,则相乘的结果越接近于A。由此可见,SVD可以用来作为特征提取的方法,机器学习中的PCA主成分分析法就是用的这种方法。但是这种方法得到的特征是各个特征之间隐含的联系构成的新特征,这样的新特征不具备可解释性。
隐语义分析LDA是一种潜在语义索引方法,一种新的信息检索代数模型,使用统计计算的方法对大量的文本集进行分析,从而提取出词与词之间潜在的语义结构,并用这种潜在的语义结构,来表示词和文本,达到消除词之间的相关性和简化文本向量实现降维的目的。
基本观点是,把高维的向量空间模型(VSM)表示中的文档映射到低维的潜在语义空间中,映射是通过对文档矩阵进行奇异值分解来实现的,SVD得到Σ,只保留最大的k个奇异值得到Σ',进行奇异值分解的反运算,得到A的近似矩阵。基本步骤如下:
1、建立词频矩阵frequency matrix
2、计算frequency matrix的奇异值分解
3、对于每一个文档d,用排除了SVD中消除后的词的新向量替换原有的向量
4、用转换后的文档索引和相似度计算
每个文档可以用三维的向量表示出来。LSA本质上识别了以文档为单位的多个单词并归入同一个子空间,在这个例子中,即为三维空间中相近的点(stock和market)在这一系列数据中存在着隐含的关系。
后两种方法是对以上方法的改进,我做的项目中没有用到,这里就不做详细的解释,感兴趣的读者可以自行查询。
三、奇异值分解的实现
参考链接:奇异值分解
# encoding=utf8
import numpy as np
from numpy import *
from numpy import linalg as la
from operator import itemgetter
def loadExData():
return [[4,4,0,2,2],
[4,0,0,3,3],
[4,0,0,1,1],
[1,1,1,2,0],
[2,2,2,0,0],
[1,1,1,0,0],
[5,5,5,0,0]]
def loadExData2():
return[[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
[0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
[0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
[3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
[5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
[0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
[4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
[0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
[0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
[0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]
def ReconstructSigma(Sigma):
return np.mat([[Sigma[0],0,0],[0,Sigma[1],0],[0,0,Sigma[2]]])
def ReconstructData(U,Sigma,VT):
return U[:,:3]*Sigma*VT[:3,:]
# 计算相似性函数
def eulidSim(inA,inB):
return 1.0/(1.0 + la.norm(inA - inB))#默认计算列做为一个元素之间的距离
def pearsSim(inA,inB):
if(len(inA)<3): return 1.0
return 0.5 + 0.5*np.corrcoef(inA, inB, rowvar=0)[0][1]# 这里返回是一个矩阵,只拿第一行第二个元素
def cosSim(inA,inB):
num = float(inA.T * inB)
denom = la.norm(inA) * la.norm(inB)
return 0.5 + 0.5 * (num/denom)
'''''
standEst 需要做的就是估计user 的item 评分,
采用方法是 根据物品相似性,及每一列相似性
要估计item那一列与其他列进行相似性估计,获得两列都不为0的元素计算相似性
然后用相似性乘以 评分来估计未评分的数值 。
'''
def standEst(dataMat,user,simMeas,item):
n = np.shape(dataMat)[1]
simTotal = 0.0 ; ratSimTotal = 0.0
for j in range(n):
userRating = dataMat[user,j]
if(userRating == 0): continue
overLap = nonzero(logical_and(dataMat[:,item].A > 0,dataMat[:,j].A >0))[0]# 返回元素不为0的下标
'''''
nonzero 返回参考下面例子,返回二维数组,第一维是列方向,第二位是行方向
'''
if(len(overLap)) == 0 :similarity = 0
else:
similarity = simMeas(dataMat[overLap,item],dataMat[overLap,j])
print 'the %d and %d similarity is : %f' %(item,j,similarity)
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0: return 0
else : return ratSimTotal/simTotal
def recommend(dataMat,user ,N = 3,simMeas= cosSim,estMethod = standEst):
unratedItems = nonzero(dataMat[user,:].A == 0)[1]# .A 使得矩阵类型转为array
'''''
>>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> a > 3
array([[False, False, False],
[ True, True, True],
[ True, True, True]], dtype=bool)
>>> np.nonzero(a > 3)
(array([1, 1, 1, 2, 2, 2]), array([0, 1, 2, 0, 1, 2]))
'''
if len(unratedItems) == 0: return 'you rated everything'
itemScores = []
for item in unratedItems:
estimatedScore = estMethod(dataMat,user,simMeas,item)
itemScores.append((item,estimatedScore))
return sorted(itemScores,key=itemgetter(1),reverse = True)[:N]
def svdEst(dataMat , user, simMeas,item):
n = shape(dataMat)[1]
simTotal = 0.0 ; ratSimTotal = 0.0
U,Sigma,VT = la.svd(dataMat)
Sig4 = mat(eye(4) * Sigma[:4]) # 保留最大三个奇异值
xformedItems = dataMat.T * U[:,:4] * Sig4.I
print xformedItems
for j in range(n):
userRating = dataMat[user,j]
if userRating == 0 or j==item: continue
similarity = simMeas(xformedItems[item,:].T,\
xformedItems[j,:].T)
print 'the %d and %d similarity is: %f' % (item, j, similarity)
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0: return 0
else: return ratSimTotal/simTotal
if __name__=="__main__":
'''''
# 测试中间数据
Data = loadExData()
MatData = np.mat(Data)
U,Sigma,VT = np.linalg.svd(Data)
print Sigma
Sigma = ReconstructSigma(Sigma)
print Sigma
print ReconstructData(U, Sigma, VT)
print eulidSim(MatData[:,0], MatData[:,4])
print cosSim(MatData[:,0], MatData[:,4])
print pearsSim(MatData[:,0], MatData[:,0])
'''
Data = loadExData()
dataMat = np.mat(Data)
dataMat2 = mat(loadExData2())
print dataMat2
print recommend(dataMat2, 1,estMethod=svdEst)