diff --git a/docs/14.利用SVD简化数据.md b/docs/14.利用SVD简化数据.md index 2382e1af..1765dbe5 100644 --- a/docs/14.利用SVD简化数据.md +++ b/docs/14.利用SVD简化数据.md @@ -155,7 +155,114 @@ def loadExData2(): > 分析数据: 暂时不需要 -> 使用算法: 通过调用 recommend() 函数进行推荐 +> 训练算法: 通过调用 recommend() 函数进行推荐 + +* 基于物品相似度(参考地址:http://www.codeweblog.com/svd-%E7%AC%94%E8%AE%B0/) + +![基于物品相似度](/images/14.SVD/基于物品相似度.png) + +```python +# 基于物品相似度的推荐引擎 +def standEst(dataMat, user, simMeas, item): + """standEst(计算某用户未评分物品中,以对该物品和其他物品评分的用户的物品相似度,然后进行综合评分) + + Args: + dataMat 训练数据集 + user 用户编号 + simMeas 相似度计算方法 + item 未评分的物品编号 + Returns: + ratSimTotal/simTotal 评分(0~5之间的值) + """ + # 得到数据集中的物品数目 + n = shape(dataMat)[1] + # 初始化两个评分值 + simTotal = 0.0 + ratSimTotal = 0.0 + # 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较) + for j in range(n): + userRating = dataMat[user, j] + # 如果某个物品的评分值为0,则跳过这个物品 + if userRating == 0: + continue + # 寻找两个用户都评级的物品 + # 变量 overLap 给出的是两个物品当中已经被评分的那个元素的索引ID + # logical_and 计算x1和x2元素的真值。 + overLap = nonzero(logical_and(dataMat[:, item].A > 0, dataMat[:, j].A > 0))[0] + # 如果相似度为0,则两着没有任何重合元素,终止本次循环 + if len(overLap) == 0: + similarity = 0 + # 如果存在重合的物品,则基于这些重合物重新计算相似度。 + else: + similarity = simMeas(dataMat[overLap, item], dataMat[overLap, j]) + # print 'the %d and %d similarity is : %f'(iten,j,similarity) + # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积 + # similarity 用户相似度, userRating 用户评分 + simTotal += similarity + ratSimTotal += similarity * userRating + if simTotal == 0: + return 0 + # 通过除以所有的评分总和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序 + else: + return ratSimTotal/simTotal +``` + +* 基于SVD(参考地址:http://www.codeweblog.com/svd-%E7%AC%94%E8%AE%B0/) + +![基于SVD.png](/images/14.SVD/基于SVD.png) + +```python +# 基于SVD的评分估计 +# 在recommend() 中,这个函数用于替换对standEst()的调用,该函数对给定用户给定物品构建了一个评分估计值 +def svdEst(dataMat, user, simMeas, item): + """svdEst(计算某用户未评分物品中,以对该物品和其他物品评分的用户的物品相似度,然后进行综合评分) + + Args: + dataMat 训练数据集 + user 用户编号 + simMeas 相似度计算方法 + item 未评分的物品编号 + Returns: + ratSimTotal/simTotal 评分(0~5之间的值) + """ + # 物品数目 + n = shape(dataMat)[1] + # 对数据集进行SVD分解 + simTotal = 0.0 + ratSimTotal = 0.0 + # 奇异值分解 + # 在SVD分解之后,我们只利用包含了90%能量值的奇异值,这些奇异值会以NumPy数组的形式得以保存 + U, Sigma, VT = la.svd(dataMat) + + # # 分析 Sigma 的长度取值 + # analyse_data(Sigma, 20) + + # 如果要进行矩阵运算,就必须要用这些奇异值构建出一个对角矩阵 + Sig4 = mat(eye(4) * Sigma[: 4]) + # 利用U矩阵将物品转换到低维空间中,构建转换后的物品(物品+4个主要的特征) + xformedItems = dataMat.T * U[:, :4] * Sig4.I + # 对于给定的用户,for循环在用户对应行的元素上进行遍历, + # 这和standEst()函数中的for循环的目的一样,只不过这里的相似度计算时在低维空间下进行的。 + for j in range(n): + userRating = dataMat[user, j] + if userRating == 0 or j == item: + continue + # 相似度的计算方法也会作为一个参数传递给该函数 + similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T) + # for 循环中加入了一条print语句,以便了解相似度计算的进展情况。如果觉得累赘,可以去掉 + 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 +``` + +排序获取最后的推荐结果 ```python # recommend()函数,就是推荐引擎,它默认调用standEst()函数,产生了最高的N个推荐结果。 @@ -178,6 +285,9 @@ def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst): return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N] ``` +> 测试 和 使用 该算法,可以自行编写 + +[完整代码地址](https://github.com/apachecn/MachineLearning/blob/master/src/python/14.SVD/svdRecommend.py): #### 要点补充 @@ -201,6 +311,97 @@ def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst): ### 项目案例: 基于 SVD 的图像压缩 +> 收集 并 准备数据 + +将文本数据转化为矩阵 + +```python +# 加载并转换数据 +def imgLoadData(filename): + myl = [] + # 打开文本文件,并从文件以数组方式读入字符 + for line in open(filename).readlines(): + newRow = [] + for i in range(32): + newRow.append(int(line[i])) + myl.append(newRow) + # 矩阵调入后,就可以在屏幕上输出该矩阵 + myMat = mat(myl) + return myMat +``` + +> 分析数据: 分析 Sigma 的长度个数 + +通常保留矩阵 80% ~ 90% 的能量,就可以得到重要的特征并取出噪声。 + +```python +def analyse_data(Sigma, loopNum=20): + """analyse_data(分析 Sigma 的长度取值) + + Args: + Sigma Sigma的值 + loopNum 循环次数 + """ + # 总方差的集合(总能量值) + Sig2 = Sigma**2 + SigmaSum = sum(Sig2) + for i in range(loopNum): + SigmaI = sum(Sig2[:i+1]) + ''' + 根据自己的业务情况,就行处理,设置对应的 Singma 次数 + + 通常保留矩阵 80% ~ 90% 的能量,就可以得到重要的特征并取出噪声。 + ''' + print '主成分:%s, 方差占比:%s%%' % (format(i+1, '2.0f'), format(SigmaI/SigmaSum*100, '4.2f')) +``` + +> 使用算法: 对比使用 SVD 前后的数据差异对比,对于存储大家可以试着写写 + +```python +# 打印矩阵 +def printMat(inMat, thresh=0.8): + # 由于矩阵保护了浮点数,因此定义浅色和深色,遍历所有矩阵元素,当元素大于阀值时打印1,否则打印0 + for i in range(32): + for k in range(32): + if float(inMat[i, k]) > thresh: + print 1, + else: + print 0, + print '' + + +# 实现图像压缩,允许基于任意给定的奇异值数目来重构图像 +def imgCompress(numSV=3, thresh=0.8): + """imgCompress( ) + + Args: + numSV Sigma长度 + thresh 判断的阈值 + """ + # 构建一个列表 + myMat = imgLoadData('input/14.SVD/0_5.txt') + + print "****original matrix****" + # 对原始图像进行SVD分解并重构图像e + printMat(myMat, thresh) + + # 通过Sigma 重新构成SigRecom来实现 + # Sigma是一个对角矩阵,因此需要建立一个全0矩阵,然后将前面的那些奇异值填充到对角线上。 + U, Sigma, VT = la.svd(myMat) + # SigRecon = mat(zeros((numSV, numSV))) + # for k in range(numSV): + # SigRecon[k, k] = Sigma[k] + + # 分析插入的 Sigma 长度 + analyse_data(Sigma, 20) + + SigRecon = mat(eye(numSV) * Sigma[: numSV]) + reconMat = U[:, :numSV] * SigRecon * VT[:numSV, :] + print "****reconstructed matrix using %d singular values *****" % numSV + printMat(reconMat, thresh) +``` + +[完整代码地址](https://github.com/apachecn/MachineLearning/blob/master/src/python/14.SVD/svdRecommend.py): * * * diff --git a/images/14.SVD/基于SVD.png b/images/14.SVD/基于SVD.png new file mode 100644 index 00000000..610219d5 Binary files /dev/null and b/images/14.SVD/基于SVD.png differ diff --git a/images/14.SVD/基于物品相似度.png b/images/14.SVD/基于物品相似度.png new file mode 100644 index 00000000..5c14e779 Binary files /dev/null and b/images/14.SVD/基于物品相似度.png differ diff --git a/input/14.SVD/0_5.txt b/input/14.SVD/0_5.txt new file mode 100755 index 00000000..598a8c0d --- /dev/null +++ b/input/14.SVD/0_5.txt @@ -0,0 +1,32 @@ +00000000000000110000000000000000 +00000000000011111100000000000000 +00000000000111111110000000000000 +00000000001111111111000000000000 +00000000111111111111100000000000 +00000001111111111111110000000000 +00000000111111111111111000000000 +00000000111111100001111100000000 +00000001111111000001111100000000 +00000011111100000000111100000000 +00000011111100000000111110000000 +00000011111100000000011110000000 +00000011111100000000011110000000 +00000001111110000000001111000000 +00000011111110000000001111000000 +00000011111100000000001111000000 +00000001111100000000001111000000 +00000011111100000000001111000000 +00000001111100000000001111000000 +00000001111100000000011111000000 +00000000111110000000001111100000 +00000000111110000000001111100000 +00000000111110000000001111100000 +00000000111110000000011111000000 +00000000111110000000111111000000 +00000000111111000001111110000000 +00000000011111111111111110000000 +00000000001111111111111110000000 +00000000001111111111111110000000 +00000000000111111111111000000000 +00000000000011111111110000000000 +00000000000000111111000000000000 diff --git a/src/python/14.SVD/svdRecommend.py b/src/python/14.SVD/svdRecommend.py index 669429aa..e2a905f6 100644 --- a/src/python/14.SVD/svdRecommend.py +++ b/src/python/14.SVD/svdRecommend.py @@ -150,11 +150,22 @@ def svdEst(dataMat, user, simMeas, item): # 奇异值分解 # 在SVD分解之后,我们只利用包含了90%能量值的奇异值,这些奇异值会以NumPy数组的形式得以保存 U, Sigma, VT = la.svd(dataMat) + + # # 分析 Sigma 的长度取值 + # analyse_data(Sigma, 20) + # 如果要进行矩阵运算,就必须要用这些奇异值构建出一个对角矩阵 Sig4 = mat(eye(4) * Sigma[: 4]) - # 利用U矩阵将物品转换到低维空间中,构建转换后的物品 + + # 利用U矩阵将物品转换到低维空间中,构建转换后的物品(物品+4个主要的特征) xformedItems = dataMat.T * U[:, :4] * Sig4.I - # 对于给定的用户,for循环在用户对应行的元素上进行遍历, + print 'dataMat', shape(dataMat) + print 'U[:, :4]', shape(U[:, :4]) + print 'Sig4.I', shape(Sig4.I) + print 'VT[:4, :]', shape(VT[:4, :]) + print 'xformedItems', shape(xformedItems) + + # 对于给定的用户,for循环在用户对应行的元素上进行遍历 # 这和standEst()函数中的for循环的目的一样,只不过这里的相似度计算时在低维空间下进行的。 for j in range(n): userRating = dataMat[user, j] @@ -205,7 +216,41 @@ def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst): return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N] +def analyse_data(Sigma, loopNum=20): + """analyse_data(分析 Sigma 的长度取值) + + Args: + Sigma Sigma的值 + loopNum 循环次数 + """ + # 总方差的集合(总能量值) + Sig2 = Sigma**2 + SigmaSum = sum(Sig2) + for i in range(loopNum): + SigmaI = sum(Sig2[:i+1]) + ''' + 根据自己的业务情况,就行处理,设置对应的 Singma 次数 + + 通常保留矩阵 80% ~ 90% 的能量,就可以得到重要的特征并取出噪声。 + ''' + print '主成分:%s, 方差占比:%s%%' % (format(i+1, '2.0f'), format(SigmaI/SigmaSum*100, '4.2f')) + + # 图像压缩函数 +# 加载并转换数据 +def imgLoadData(filename): + myl = [] + # 打开文本文件,并从文件以数组方式读入字符 + for line in open(filename).readlines(): + newRow = [] + for i in range(32): + newRow.append(int(line[i])) + myl.append(newRow) + # 矩阵调入后,就可以在屏幕上输出该矩阵 + myMat = mat(myl) + return myMat + + # 打印矩阵 def printMat(inMat, thresh=0.8): # 由于矩阵保护了浮点数,因此定义浅色和深色,遍历所有矩阵元素,当元素大于阀值时打印1,否则打印0 @@ -220,25 +265,30 @@ def printMat(inMat, thresh=0.8): # 实现图像压缩,允许基于任意给定的奇异值数目来重构图像 def imgCompress(numSV=3, thresh=0.8): + """imgCompress( ) + + Args: + numSV Sigma长度 + thresh 判断的阈值 + """ # 构建一个列表 - myl = [] - # 打开文本文件,并从文件以数组方式读入字符 - for line in open('testData/testDigits/0_5.txt').readlines(): - newRow = [] - for i in range(32): - newRow.append(int(line[i])) - myl.append(newRow) - # 矩阵调入后,就可以在屏幕上输出该矩阵 - myMat = mat(myl) + myMat = imgLoadData('input/14.SVD/0_5.txt') + print "****original matrix****" # 对原始图像进行SVD分解并重构图像e printMat(myMat, thresh) + # 通过Sigma 重新构成SigRecom来实现 # Sigma是一个对角矩阵,因此需要建立一个全0矩阵,然后将前面的那些奇异值填充到对角线上。 U, Sigma, VT = la.svd(myMat) - SigRecon = mat(zeros((numSV, numSV))) - for k in range(numSV): - SigRecon[k, k] = Sigma[k] + # SigRecon = mat(zeros((numSV, numSV))) + # for k in range(numSV): + # SigRecon[k, k] = Sigma[k] + + # 分析插入的 Sigma 长度 + analyse_data(Sigma, 20) + + SigRecon = mat(eye(numSV) * Sigma[: numSV]) reconMat = U[:, :numSV] * SigRecon * VT[:numSV, :] print "****reconstructed matrix using %d singular values *****" % numSV printMat(reconMat, thresh)