一个小的推荐系统示例

   Python是能编译的伪代码,开发效率高,计算能力很强,变量不用声明。还可以写脚本、写网站(主要是在html里面写python,估计没人会在Python里面写html吧)

一、Python也有类似java的机制——“PVM”(大家理解成python虚拟机,可以在多平台下运行)

二、环境搭建:(1)在windows下,直接上官网www.python.org 下载msi文件安装即可,可以用命令行,也有自己的GUI界面——IDLE;(2)如果是linux,那就自己找安装教程吧,也很简单的,如:在ubuntu下直接在shell下敲"apt-get install python2" ;3)总之环境搭建很简单。

三、实验数据

  一个测试集(80train.txt8万行数据),一个训练集(test.txt2万行数据),这两个数据都是来自某电影推荐网站的数据库,其中每一行有四个字段(196242 3 881250949),字段依次表示usermoviescoretime(单位:ms,经过分发现最后一个字段是没有什么用处的)。算法会从训练集中找到用户之间的相似度(这里是简化的皮尔逊算法),根据相似度*分数,然后求平均值得到推荐分数。测试集是用来检查你的预测是否正确的,标准为MAEMean Average Error=(所有预测值跟真实值差值总和)/(预测数量),越接近0越准确

四、代码

# -*- coding: cp936 -*-
from math import *
import time
import random

##读取文件,返回值为嵌套字典
def Readf(filename):
    dir={}
    tmp={}
    
    #读取文件
    fp=open(filename, "r")
    alllines=fp.readlines()
    fp.close()
    
    #读取每一行数据,忽略时间戳(应该说时间戳没有用、、)
    for eachline in alllines:
        item=eachline.split()
        if dir.has_key(item[0]):
            tmp=dir[item[0]]
        else:
            tmp={}
        tmp[item[1]]=int(item[2])
        dir[item[0]]=tmp
    return  dir

##相似度算法,返回值越大说明越相近(r=range(-1,1))
def simplePEX(prefs,p1,p2):
    si={}
    for item in prefs[p1]:
        if item in prefs[p2]: si[item]=1
    n=len(si)                     #相同电影的数量
    if n==0: return 1
    
    #偏好求和
    sum1=sum([prefs[p1][it] for it in si])
    sum2=sum([prefs[p2][it] for it in si])
    
    #求平方和
    sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
    sum2Sq=sum([pow(prefs[p2][it],2) for it in si])
    
    #求乘积和
    pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
    
    #计算皮尔逊评价值
    num=pSum-(sum1*sum2/n)
    den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
    
    if den==0:
        return 0
    r=num/den
    #简化皮尔逊,返回保留一位小数的浮点数
    return int(r*10+1)*0.1

##得到用户的所有电影的平均分
def getUserAverage(prefs,user):
    scoresSum=0
    count=0
    for movie in prefs[user]:
            scoresSum += prefs[user][movie]
            count +=1
    return scoresSum/count

def getRecommendations(prefs,person,similarity=simplePEX):
    totals={}
    simSums={}
    #不要和自己比较
    userAve=getUserAverage(prefs,person)
    for other in prefs:
        if other==person: continue
        otherAve=getUserAverage(prefs,other)
        sim=similarity(prefs,person,other)
        
    #忽略评价值小于0.3的情况
        if sim<=0.2: continue
        
        for othermovie in prefs[other]:
          if othermovie not in prefs[person]:
            totals.setdefault(othermovie,0)
            totals[othermovie]+=(prefs[other][othermovie]-otherAve)*sim
            simSums.setdefault(othermovie,0)
            simSums[othermovie]+=sim
    rankings=[(int(total/simSums[item]+userAve+0.5),item) for item,total in totals.items()]
    rankings.sort()
    rankings.reverse()
    return rankings
            

def printMAE():
    _sum=0.0
    count=0
    train=Readf("80train.txt")
    test=Readf("test.txt")
    
    for person in test:
        recommentations = getRecommendations(train,person)
        for key in test[person].keys():
              itemCount = 0
              for item in recommentations:
                  if key == item[1]:
                     _sum += abs(item[0] - test[person][key])
                     count += 1
                     break
                  itemCount += 1
                  if itemCount == len(recommentations): 
                      _sum += getUserAverage(train,person)
                      count += 1
                      
    if count==0: print "error!"
    else:
        print "Count",count
        print "MAE =",_sum/count


#here is main()
start=time.time()
print "Start:"
printMAE()
end=time.time()
print "Time=",end - start,"s"
print "End!"


五、数据处理过程

1. 数据预处理。将 80train.txt 和 test.txt 的条目(每一行)一条一条进行处理,忽略时间戳生成python的数据结构——dictionary,其结构如下:

         { user0{movie0scores0moive1scores1......}user1:{movie0scores0......} ...... }

2. 脏数据去除。本来以为时间戳可以去掉一些脏数据。但是发现时间戳根本没有什么用处(理由在下面),所以我认为所有的数据都是有用的。

那么为什么没用?

① 如果时间戳的单位为ms

Name : 216

Movie:

      658  3  1970-01-11 04:30:45

Name : 217

Movie:

      27  1  1970-01-11 06:57:50

      2  3  1970-01-11 06:57:49 ......

结果时间值为1970111号凌晨左右。

② 那如果单位是s呢?

Name : 216

Movie:

      658  3  1997-11-23 00:30:29

Name : 217

Movie:

      27  1  1998-03-05 03:53:31

      2  3  1998-03-05 03:49:42

Name : 214

Movie:

      1129  4  1998-04-15 19:24:09

      334  3  1998-04-02 18:42:20

      117  4  1998-04-02 18:54:01

      131  3  1998-04-02 19:14:25

      137  4  1998-04-02 18:53:47

      275  3  1998-04-02 18:49:28

Name : 215

Movie:

      421  4  1998-04-01 13:01:44

      197  4  1998-04-01 12:55:57

      212  2  1998-04-01 13:01:20

   输出的结果为19981月至12月。虽然时间不同,但是对于特定的某一个用户,时间是基本一样的。这些数据很明显跟用户没有关系,因为整个程序是基于用户的,所以我觉得时间戳跟用户没有关系。通俗一点,时间戳没有什么作用。

用户间的相似度。采用的是皮尔逊算法,返回值大小区间为[-1,1]。值越大,用户的相似程度越高。

3. 简化皮尔逊算法。普通皮尔逊的返回值为[-1,1]的浮点数。这里,我们只采用后小数点后一位

4. 但是真正能用的只能是[0,1],这是之前的想法,但是考虑到当值用户之间的相似度很小的时候(也是正数),我们大胆推测其间已经不是很相似了,换句话说就是“不相似值”已经很接近“1”了,对于这部分数据(相似度较小),我们最终采取去除的操作。去除方法:本来的想法是去除[0,1]相似值较小的n%的,但这个比较耗运行时间。最终方法是只选用相似值为[0.3 , 1]的数据

5. 推荐算法的实现。对于一个特定的人,例如:用户:“20”,电影:“211”。那么我们对每个除自己外的人算皮尔逊值,选取[0.3 , 1]区间的数据。然后在每个其他用户的字典里找电影“211”,如果存在“211”,那么读取该用户的“211”评分,乘于相似度,最终取所有平均值。返回。

6. MAE。读取test.txt 产生的字典,循环读取每个用户的每部电影,然后对推荐评分和test评分进行处理,最终得出MAE。但是并不是每一部电影都有推荐,那么这是为什么呢?因为有些电影没有人看过,没有看过的电影怎么推荐呢?这个问题我也问过老师,老师说了两种方法:1.给中间值;2.给最常出现的的评分。而我想了一个个人觉得更好的方法——给该用户所有电影的最低分。理由:之所以没有算出推荐分数,不是因为没有没人看过这部电影,而是因为有该电影评分的没人和有相似度,有相似度的却没看过这部电影。在现实生活中是有可能发生的,不常见而已。

六、输出结果

Start:
Count 20000
MAE = 0.7616
Time= 67.7969999313 s
End!

欢迎下载数据代码:文件名:Data Mining-python.rar,访问地址:pan.xiaomi.com/file/id_59304594890555457.htm(小米网盘,记得加http://)

    在下才疏学浅,上面是我的一些私人分享,如有不正确的地方,还请留言指正。——jack

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐