Bootstrap

推荐系统 基础入门

1.推荐系统对基础

结构化问题:将积累的知识加以归纳和整理,使之条理化,能纲举目张。

推荐系统:将用户和内容链接起来,使用特定逻辑,给用户产生内容推荐

区别:

1.结构化问题指的是可以将解决问题得到的方法,知识,累积起来,来为其他问题    

提供参考

2.推荐系统是针对用户的兴趣进行建模,为其提供感兴趣的商品,虽然推荐逻辑是

不变的,但用户的兴趣会不断的变化,因此要不断的调整对用户的信息推荐

1.通过硬指标,

如根据用户在网站或app上的点击率的情况(增长,减少)等情况,来判断推荐是否得

到用户喜爱。

使用准确率,召回率,ROC,以及AUC等指标进行衡量。

2.使用线下用户,通过小规模反复的白板测试,对用户的体验进行调查。

  • & 排序,为什么这样划分

在推荐系统中,用户的静态属性,内容,用户和内容的交互,产生了巨大的数据

量。若将所有的数据量带入推荐模型中,对硬件资源和计算资源的要求比较高,需

要较长的时间来产生结果,若再对结果进行评判,调整模型,迭代系统,整个流程

所带来的时间花费较大,为此使用特定的召回策略,从数以千万级别的数据

中,选出数以百计的特定信息,以此为基础向用户产生特定推荐,一方面降低了整

个系统迭代,调整需要花费的时间,另一方面也降低了对硬件和计算资源的要求。

运行推荐模型后会获取一系列特定用户的推荐结果,但用户对这些推荐结果的感兴

趣程度并不一样,将感兴趣程度更高的推荐结果放在前方,能够有效的提高用户的

使用体验,帮助用户推荐更有用的商品。

——————————————————————————————————————————————————

企业级推荐系统为什么拆解为召回、排序两个阶段?

企业级推荐系统为什么拆解为召回、排序两个阶段?_数据与智能的博客-CSDN博客

召回:使用各种方法,从物品库选出用户可能喜欢的商品,召回几十到几百个商品

排序:将不同召回算法得到的结果,重新打分排序,按照分数高低推荐给用户

之所以拆解成两个阶段:

1.将算法流程解耦合,分而治之,工程分割

2.便于分开实现

3.提升推荐精准度,有问题的话分开调整

--------------------------------------------------------------------------------------------------------

任务二:数据集Movienles

  • Movielens 1M数据集(用户、电影、评分)
    • pandas库,读取文件

users = pd.read_csv(r"/Users/wudun/Downloads/fun-rec-master 2/codes/base_models/data/ml-1m/users.dat",sep='::', header=None, engine='python', encoding='utf-8').to_numpy()

#sep表示数据划分间隔符号,

#header 表示第一行数据是否作为列名

#engine 表示语言引擎选择,C最快,pyhton最全

#encoding 表示编码形式

#to_numpy() 转换成numpy

rating = pd.read_csv(r"/Users/wudun/Downloads/fun-rec-master 2/codes/base_models/data/ml-1m/ratings.dat",sep='::', header=None, engine='python', encoding='utf-8').to_numpy()

movie = pd.read_csv(r"/Users/wudun/Downloads/fun-rec-master 2/codes/base_models/data/ml-1m/movies.dat",sep='::',on_bad_lines='warn' header=None, engine='python', encoding='utf-8').to_numpy()

#pandas读取movies数据时,该表中在第11行期待出现一个,实际出现两个数据,导致pandas不知道该如何处理

#解决方法:

#pandas版本在1.4.2 使用关键词on_bad_lines='warn',在遇到太多字段的行时,进行警告,并跳过

#处理用户数据,

#users 数据描述:

#用户文件有以下字段,分别是用户ID、性别、年龄、职业和邮编( UserID::Gender::Age::Occupation::Zip-code )

#处理思路:

#将用户性别 M、F 进行  encoding 编码

#将年龄 encoding 编码

#邮编取前3位

for user in users:  # 将年龄 Lable Encoding

    if user[2] == 1:

        user[2] = 0

    elif user[2] == 18:

        user[2] = 1

    elif user[2] == 25:

        user[2] = 2

    elif user[2] == 35:

        user[2] = 3

    elif user[2] == 45:

        user[2] = 4

    elif user[2] == 50:

        user[2] = 5

    elif user[2] == 56:

        user[2] = 6

    if user[1] == 'F':#将性别 encoding

        user[1] = 1

    elif user[1] == 'M':

        user[1] = 0

    user[4] = int(user[4][0:3]) - 1 # 取邮编的前3位

    user[0] += 1 #从 0 开始,用户编号

#获取用户量

len(users)

#是否可以调用pandasAPI来对数据进行处理,而不是使用for循环

#movies 数据描述:

#电影文件包含三个字段,分别是电影ID、电影名、电影类型( MovieID::Title::Genres )

#处理思路:

#将电影名称删除,电影类型在合并users、movies和ratings之后做 one-hot 处理。

movies = np.delete(movies, 1, axis=1) # 删除电影名称列  

movies[:, 0] -= 1

#电影个数

len(movie)

#ratings 数据描述:

#电影评分文件包含用户ID、电影ID、评分和时间戳

#( UserID::MovieID::Rating::Timestamp )

#处理思路:

#将时间戳字段删除

#将评分大于3的作为正样本(记为1),评分小于等于3的作为负样本(记为0)

#多少个用户对多少个电影进行了评分?

rating = pd.DataFrame(rating)

len(rating[0].unique())

len(rating[1].unique())

    • & 每个用户的平均评分是?

#每部电影的平均评分

#解决思路:

h=rating[1].unique()#获取所有电影号

R=rating[0].unique()#获取所有用户

#对用户 和电影号排序

h=np.sort(h)

R=np.sort(R)

#1.求每个用户的平均评分,找出该用户的所有电影评分,除以电影数目

#计算用户对电影的平均评分

for i in R:

    index = 0 #测量用户评分的电影数目

    sum = 0 #测量用户对电影评分的分数和

    for j in range(len(rating)):

        if rating.iloc[j,0] == i:

            index +=1

            sum += rating.iloc[j,2]

    print("用户{0}评论了{2}个电影,总共评分为{1},平均评分为{3}".format(i,sum,index,sum /index))

#读取dataframe框架中的特定行列的某一数据,可以使用loc或iloc属性

#直接按照数组来调用特定位置的数据,会无法按照整型进行数据计算

#更优质对代码

#调用API来完成的获取用户对平均评分

#basescore = np.mean(np.array([value for value in users[j].values()]))、#函数关系公式需要用中括号括起来

#base = np.mean(np.array([value for value in users[j].values()]))

#2.求每个电影的平均评分,找出该电影的所有用户评分,除以用户数目

for i in h:

    index = 0 #测量用户评分的电影数目

    sum = 0 #测量用户对电影评分的分数和

    for j in range(len(rating)):

        if rating.iloc[j,1] == i:

            index +=1

            sum += rating.iloc[j,2]

    print("电影{0}获得了{2}用户评论了,总共获得评分为{1},平均评分为{3}".format(i,sum,index,sum /index))

#np.mean(np.array([value for value in users[j].values()]))

#将处理好的rating、movies、user三个数据集进行合并

unames = ['userId', 'gender', 'age', 'occupation', 'zipCode']

mnames = ['movieId', 'genres']

rnames = ['userId', 'movieId', 'rating']

users = pd.DataFrame(users, columns=unames)

movies = pd.DataFrame(movies, columns=mnames)

ratings = pd.DataFrame(ratings, columns=rnames)

data = pd.merge(movies, ratings, on=['movieId'])

data = pd.merge(users, data, on=['userId'])

#合并两边,链接轴在on属性上标示

#将合并后的数据集,

#1.使用sklearn库中的函数,以7比3的方式进行分割

x_train, x_test,  y_train, y_test = train_test_split(data, userID, test_size = 0.3, random_state = 7)

#2.自行划分

————————————————

随机划分 和 对折交叉

--------------------------------------------------------------------------------------------------------

  • 3和4:协同过滤基础与进阶

协同过滤算法:基于用户行为数据,不依赖其他信息给用户推荐物品

常见的算法有

基于用户的协同过滤:给用户推荐和他相似的其他用户喜欢的商品

基于物品的协同过滤:给用户推荐他喜欢商品的类似商品

userCF的实现逻辑:

1.衡量目标用户与其他用户的相似度,找到与目标用户最相似的n个用户

a.相似度的度量方法:

杰卡德相似系数,余xian相似度,皮尔逊相关系数

杰卡德相似系数:两个用户交互商品交集的数量占这两个用户交互商品并集的数

量的比例,常用来评估用户是否会对商品进行打分。

余xian相似度:衡量了两个向量的夹角,夹角越小越相似。

from sklearn.metrics.pairwise import cosine_similarity

i = [1, 0, 0, 0]

j = [1, 0.5, 0.5, 0]

consine_similarity([a, b])

皮尔逊相关系数:皮尔逊相关系数通过使用用户的平均分对各独立评分进行修正,减小

了用户评分偏置的影响。

from scipy.stats import pearsonr

i = [1, 0, 0, 0]

j = [1, 0.5, 0.5, 0]

pearsonr(i, j)

import numpy as np

import pandas as pd

import math

def mn(x):#使用数组中的每个元素减去平均值

    y = {}

    mean = np.mean(x)

    print("x的均值={:.2f}".format(mean))

    for i in range(len(x)):

        y[i]=float(x[i])-mean

        print("减均值后{0}={1}".format(i,y[i]))

        #疑惑 为啥该公式减法会自动保留整数,是因为数组类型的原因吗?,经调整修改数组类型后,数组的元素个数发生变化,不应

       该出现的问题,

        #测试,计算结果并无问题,但结果数值放入数组中时,会根据数组的类型进行类型调整,导致小数点后的数字全部省去

        #尝试 可建立新数组来存放数值,尝试成功

    return y

def Con_similarity(x,y):#计算两个向量之间的余弦值

    sum = 0

    s_x = 0

    s_y = 0

    if(len(x) == len(y)):

        for i in range(len(x)):

            print("第{0}个数,x{0}={1},y{0}={2}".format(i,x[i],y[i]))

            sum = sum + x[i]*y[i]

            s_x = x[i]*x[i]+s_x

            s_y = y[i]*y[i]+s_y

    print("x的平方={0},y的平方={1}".format(s_x,s_y))

    s_x = math.sqrt(s_x)

    s_y = math.sqrt(s_y)

    return float(sum/(s_x*s_y))

def P_similarity(x,y):

    x1 = mn(x)#

    y1 = mn(y)

    return Con_similarity(x,y)

#调用    

x = np.array([5,3,4,4])

y = np.array([3,1,2,3])

c = P_similarity(x,y)

print(c)

2.根据这n个相似用户对某物品对评分情况和与目标用户对相似度,来预测出目标用户对该物品

对可能评分,若可能评分较高,就把该物品推荐给用户,否则不推荐。

a.根据相似度用户计算目标用户对该物品的可能评分

b.按照a方式,计算出所有目标用户的可能商品评分

c.按照评分的排序,找出预测评分最高的商品,推荐前n个给用户

users的编程实现

a.首先,使用字典建立数据表,

没有用pandas, 是因为在真实情况下,  用户对物品的评分情况并不会完整, 存在大量的

空值, 所以若使用DataFrame, 会产生大量的NaN值。

故这里用字典的形式存储。

用两个字典, 第一个字典是物品-用户的评分映射, 键是物品1-5, 用A-E来表示, 每一个

值又是一个字典, 表示

的是每个用户对该物品的打分。

第二个字典是用户-物品的评分映射, 键是上面的五个用户, 用1-5表示, 值是该用户对每

个物品的打分。

b.计算用户相似性矩阵 

这个是一个共现矩阵, 5*5,行代表每个用户, 列代表每个用户, 值代表用户和用户的相关性.

思路: 因为要求用户和用户两两的相关性, 所以需要用双层循环遍历用户-物品评分数据,

当不是同一个用户的时候, 我们要去遍历物品-用户评分数据,

在里面去找这两个用户同时对该物品评过分的数据放入到这两个用户向量中。

 因为正常情况下会存在很多的NAN,即可能用户并没有对某个物品进行评分过,

 这样的数据不能当做用户向量的一部分, 没法计算相似性。  

c.计算前n个相似用户

d.计算最终得分

    • User-CF代码,通过用户相似度得到推荐

#UserCF的实现

#1.获取用户数据

##数据结构,数组,list,字典,

##存储?使用矩阵存储用户间的相似度,每行每列,表示第n个用户和第j个用户的相似度

##排序?将矩阵中的每一行转化成列表,列表的每个元素都有下标,对列表进行排序?,不对,

##字典?使用字典,排序的时候,根据字典的值进行排序,根据字典的key来确定用户

##使用字典或者dataframe来存储用户数据

#2.判断用户间的相似度,存储,

##使用三重for循环,前两重调用用户数据,分布调用用户数据,第三层循环获取两个用户对商品的具体评分,计算,存储进字典或dataframe

##案例使用DataFrame来构建矩阵

#3.排序用户的相似度,获取前n个用户,构建所有用户的

##根据每个用户与其他用户的相似度的值,进行排序,选取相似度最高的前n个

##新建一个空的多层字典,将上一步选取出的结果放入其中,

##将内层字典存储某用户相似度最高的前k个用户,便于后面利用,外层字典存储所有用户的前k个相似度较高的用户

##或者,其实也不用新建一个多层字典来存放数据,直接在计算出来的字典中,当需要获取前K个用户的数据时,

##直接对字典值排序,加个for,获取前k个用户的用户名,对商品的评分和对应的相似度

#4.根据前n个用户的相似度,预测评分的公式,实现该用户对某一商品的评分预测

##来个for ,计算目标用户对商品的平均分+【所有相似用户的每个最相似用户的相似度*该用户对j商品的评分-该用户评分的均值的和 】再除以相似度

##使用用户相似度和相似用户的评价加权公式获得用户的评价预测

#userCF

#第一步 获取用户数据,

def loadData():

    items = {

        'A':{1:5,2:3,3:4,4:3,5:1},

        'B':{1:3,2:1,3:3,4:3,5:5},

        'C':{1:4,2:2,3:4,4:1,5:5},

        'D':{1:4,2:3,3:3,4:5,5:2},

        'E':{2:3,3:5,4:4,5:1}

    }

    users = {

        1:{'A':5,'B':3,'C':4,'D':4},

        2:{'A':3,'B':1,'C':2,'D':3,'E':3},

        3:{'A':4,'B':3,'C':4,'D':3,'E':5},

        4:{'A':3,'B':3,'C':1,'D':5,'E':4},

        5:{'A':1,'B':5,'C':5,'D':2,'E':1},

    }

    return items,users

items , users = loadData()

it_df = pd.DataFrame(items).T

us_df = pd.DataFrame(items).T

#items["A"][1] 多层字典的数值调用

#第2步 构建用户的相似度矩阵

S_matrix = pd.DataFrame(np.zeros((len(users),len(users))),index = [1,2,3,4,5],columns = [1,2,3,4,5])

#构建空矩阵,并设置列名和行名

#遍历用户数据,计算用户间的相似度,并存放

for id in users :#从表中获取用户1

    for jd in users:#从表中获取用户2

        id_vec = []#存储id用户对商品itid的评分值

        jd_vec = []#存储jd用户对商品itid的评分值

        if id != jd:

            for itid in items:

                rating = items [itid]#获取item数据表中每一个商品的用户评分列

                if id in rating and jd in rating: #若两个用户均在该商品的评分列中,则存储两个用户分别对商品itid的评分

                    id_vec.append(rating[id])

                    jd_vec.append(rating[jd])

            S_matrix[id][jd]=np.corrcoef(np.array(id_vec),np.array(jd_vec))[0][1]

            #coeecoef函数,即皮尔逊系数,返回值为两个向量对应矩阵,对角线全为1,其余位置,表示两个向量的相关性

#第3步

j = 1#第一个用户

n = 2 #两个最相似用户

food = 'E'

S_matrix_N_user = S_matrix[j].sort_values(ascending = False)[:n].index.tolist()            

#第4步.根据前n个用户的相似度,预测评分的公式,实现该用户对某一商品的评分预测

##来个for ,计算目标用户对商品的平均分+【所有相似用户的每个最相似用户的相似度*该用户对j商品的评分-该用户评分的均值的和 】再除以相似度

base_score = np.mean(np.array([value for value in users[j].values()]))#函数关系公式需要用中括号括起来

s_sum_simlity = 0 #综合所有用户间的相似度

change_score = 0 #在均值的基础上,增加的波动值,公式计算(分子)

##使用用户相似度和相似用户的评价加权公式获得用户的评价预测

for userid in S_matrix_N_user:

    s_sum_simlity= s_sum_simlity +S_matrix[j][userid] #进行相似度的统合

    user_base = np.mean(np.array([value for value in users[userid].values()]))#获取userid用户的商品评分均值

    change_score = change_score + S_matrix[j][userid]*(users[userid][food]-user_base)

P_score = base_score + change_score/s_sum_simlity

print("经计算,用户{}对商品{}的预测评分为{}".format(j,food,P_score))                    

userCF的优劣

1.用户对商品评价相对稀缺,对电子商务系统来说,每个用户可能购买对商品数量是极为有限的,不同用户购买的商品重叠性较低,导致算法很难找到相似用户:所以该算法不适合用在正反馈获取困难的应用场景(比如酒店预定,大件商品购买等)

2.该算法需要建立用户相似度矩阵,当用户量较大时,矩阵存储开销非常大,不适合用户数据量较大的情况使用

Item-cf的实现

    • Item-CF代码,通过物品相似度得到电影推荐,直接使用python

items = np.array([[3,4,3,1],[2,4,1,5],[3,3,5,2],[3,5,4,1]])

cols = ['item'+str(i) for i  in range(1,6)]

pd.DataFrame(np.corrcoef(items),columns = cols,index = cols)

#调用函数来解决问题

itemCF的实现步骤:

1.计算目标物品与其他物品的相似度,

2.根据相似度来找到与目标物品最相近的n个商品

3.根据该目标用户的对相似商品对打分,来计算对目标物品打分情况

"""计算物品的相似矩阵"""

similarity_matrix = pd.DataFrame(np.ones((len(items), len(items))), index=['A', 'B', 'C', 'D', 'E'], columns=['A', 'B', 'C', 'D', 'E'])

# 遍历每条物品-用户评分数据

for itemId in items:

    for otheritemId in items:

        vec_item = []         # 定义列表, 保存当前两个物品的向量值

        vec_otheritem = []

        #userRagingPairCount = 0     # 两件物品均评过分的用户数

        if itemId != otheritemId:    # 物品不同

            for userId in users:    # 遍历用户-物品评分数据

                userRatings = users[userId]    # 每条数据为该用户对所有物品的评分, 这也是个字典

                

                if itemId in userRatings and otheritemId in userRatings:   # 用户对这两个物品都评过分

                    #userRagingPairCount += 1

                    vec_item.append(userRatings[itemId])

                    vec_otheritem.append(userRatings[otheritemId])

            

            # 这里可以获得相似性矩阵(共现矩阵)

            similarity_matrix[itemId][otheritemId] = np.corrcoef(np.array(vec_item), np.array(vec_otheritem))[0][1]

            #similarity_matrix[itemId][otheritemId] = cosine_similarity(np.array(vec_item), np.array(vec_otheritem))[0][1]

"""得到与物品5相似的前n个物品"""

n = 2

similarity_items = similarity_matrix['E'].sort_values(ascending=False)[:n].index.tolist()       # ['A', 'D']

"""计算最终得分"""

base_score = np.mean(np.array([value for value in items['E'].values()]))

weighted_scores = 0.

corr_values_sum = 0.

for item in similarity_items:  # ['A', 'D']

    corr_value = similarity_matrix['E'][item]            # 两个物品之间的相似性

    mean_item_score = np.mean(np.array([value for value in items[item].values()]))    # 每个物品的打分平均值

    weighted_scores += corr_value * (users[1][item]-mean_item_score)      # 加权分数

    corr_values_sum += corr_value

final_scores = base_score + weighted_scores / corr_values_sum

print('用户Alice对物品5的打分: ', final_scores)

user_df.loc[1]['E'] = final_scores

user_df

算法评估:

1.召回率

2.精确率

3.覆盖率:反映了推荐算法发掘长尾的能力, 覆盖率越高, 说明推荐算法越能将长尾中的物品推荐给用户。

4.新颖度:由于物品的流行度分布呈长尾分布, 所以为了流行度的平均值更加稳定, 在计算平均流行度时对每个物品的流行度取对数。

userCF和ItemCF的使用分析:

UserCF:适合用于用户较少,物品较多,时效性较强的影城场景,具有更多的社交特性

ItemCF:使用于兴趣变化较为稳定的场合,更接近个性化推荐,适合物品少,用户多,用户兴趣固定,且物品更新速度不快的场合

协同过滤的权重改进:

1.对热门物品的权重进行惩罚

2.对活跃用户对权重进行惩罚,活跃用户对物品相似度的贡献应该小于不活跃用户

协同过滤算法的问题分析:

1.泛化能力弱,无法将两个物品相似的信息推广到其他物品相似性上,导致热门物品的头部效应很强,容易跟大量物品相似,尾部物品则很少被推荐。

该问题可以使用矩阵分解技术(MF)在一定程度上补充缺陷。

2.协同过滤算法没有考虑到用户和物品本身的属性,造成信息泄漏,不能充分利用其他特征数据。

为了解决这个问题,在模型中引用更多特征,目前推荐系统逐步从协同过滤为核心向以逻辑回归模型为核心转变,提出能够综合不同类型特征的机器学习模型。

进阶:如果不使用矩阵乘法,你能使用倒排索引实现上述计算吗?

倒排索引:

  • 5:矩阵分解SVD

矩阵分解模型(MF),即隐语义模型,在协同过滤矩阵的基础上,使用更稠密的隐向量表示用户和物

品,挖掘用户和物品的隐含兴趣和隐含特征,在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问

题。

隐语义模型:通过隐含特征(latent factor)联系用户兴趣和物品(item),基于用户行为找出潜在的主

题和分类,然后对item进行自动聚类,划分到不同类别/主题(用户的兴趣)。其实是找到用户和物品的

可以表达兴趣,类别和偏好的隐向量,将相似的物品推荐给用户。

item的类别和风格很多,我们是无法获得用户对某些类型的偏好,和音乐或商品的成分偏向,正常情况

下,我们获得的评分矩阵都是一张二维表,表中的数据还经常存在缺失和遗漏,矩阵非常稀疏,容易出现

长尾问题。

矩阵分解模型就是基于评分矩阵,找到能够表示用户兴趣和物品类别的隐向量表达,将评分矩阵分解为

Q*R矩阵乘积的形式。基于Q和R矩阵去预测某用户对某物品的评分,基于评分去推荐。

矩阵分解算法的求解:

矩阵分解:常用特征值分解(EVD)和奇异值分解(SVD)【原理详解及推导】

EVD:要求分解的矩阵是方阵,评分矩阵不满足要求

SVD:要求分解的矩阵是稠密的,而评分矩阵一般是非常稀疏,若想使用SVD解评分矩阵,1是要将缺失值填满,

但存在补充数值不一定对,且整个矩阵的存储空间非常大,而且SVD求解方程的计算复杂度非常高,基本无法使用

以上矩阵解法不适用评分矩阵。

FunK—SVD(又称为Latent,Factor,Model:LFM):将求解Q和R矩阵的参数问题转换成一个最优化问题,通过

训练集中的观察值利用最小化来学习用户矩阵和物品矩阵。

解法:

1.我们虽然有真实的评分矩阵,但没有Q和R,我们可以随机初始化一个用户矩阵Q1和物品矩阵R1

2.计算Q1 *R1=F1,则评分矩阵F和F1之间必然存在误差C,

3.根据误差C=F-F1,求出总的误差平方和:SSE=C^2

4.想办法训练误差,将SSE降到最小,则两个矩阵参数Q和R就可以算出来,将求矩阵的问题转换为最优化问题

获取目标函数:min(F1-F)

5.有目标函数,使用梯度下降算法降低损失:

a.对目标函数求偏导,得到梯度,若目标函数为SSE

b.求Q矩阵在第U行第K列的梯度,求R第K行第i列的梯度,带入SSE公式中,

c.当用户和物品矩阵过大时,预测结果容易过拟合,在原来的基础上添加偏置向,用来消除用户

和物品打分的偏差。

class SVD():

    #初始化字典矩阵和相关的偏差系数,并随机赋值给字典矩阵中去

    def __init__(self,rating_data,F=5,alpha=0.1,lmbda=0.1,max_iter=100):

        #SVD类赋予初值

        self.F = F #这个表示隐向量维度

        self.P = dict() #用户矩阵 大小[users_num,F]

        self.Q = dict() #物品矩阵 大小[items_num,F]

        self.bu = dict()

        self.bi = dict()

        self.mu = 1.0 #全局评分系数

        self.alpha = alpha # 学习率,步长

        self.lmbda = lmbda #正则项系数

        self.max_iter = max_iter #最大迭代次数

        self.rating_data = rating_data #评分矩阵

        

        #初始化,用户矩阵和物品矩阵,使用随机数填充,经验:随机数需要与1/sqrt(F)成正比

        cnt = 0#统计总的打分个数,初始化全局评分系数 mu

        for user,items in self.rating_data.items():

            self.P[user] = [random.random()/math.sqrt(self.F) for x in range(0,F)]#使用函数表达式,一次直接赋值一个用户所有的评价

            self.bu[user] = 0

            cnt += len(items)

            for item ,rating in items.items():#使用items(),两重for可遍历多层字典

                if item  not in self.Q:

                    self.Q[item] = [random.random()/math.sqrt(self.F) for x in range(0,F)]

                    self.bi[item] = 0

        self.mu /= cnt

        #预测公式,预测用户user 对物品item的评分,未使用向量的形式

    def predict(self,user,item):

        return sum(self.P[user][f]*self.Q[item][f] for f in range(0,self.F))+self.bu[user]+self.bi[item]+self.mu

        

    #有了矩阵后,使用随机梯度下降的方式训练参数P和Q

    def train(self):

        for step in range(self.max_iter): #循环迭代次数

            for user,items in self.rating_data.items():#第一层分隔,多层字典

                for item,rui in items.items():#第二层分离字典数据,获取真实评分

                    rhat_ui = self.predict(user,item) #计算预测评分

                    e_ui = rui - rhat_ui #计算误差评分

                    #随机梯度下降方式获取偏差参数的变化

                    self.bu[user] += self.alpha*(e_ui-self.lmbda *self.bu[user])  

                    self.bi[item] += self.alpha*(e_ui-self.lmbda *self.bi[item])

                

                     #随机梯度下降更新用户预测评分;已经确定参数下降的公式可以使目标函数即误差最小,

                  # __________________这部分的情况不清楚具体的推导过程

                     #所以反过来推,用户矩阵和物品矩阵分布沿着其偏导方向,不断迭代,

                    for k in range(0,self.F):

                        self.P[user][k] += self.alpha * (e_ui * self.Q[item][k] - self.lmbda *self.P[user][k])

                        self.Q[item][k] += self.alpha * (e_ui * self.P[user][k] - self.lmbda *self.Q[item][k])

                #-------------------------------

        self.alpha *= 0.1 #每次迭代的步长

                    

        return self.P,self.Q  

#------------------------------------

def loadData():

    rating_data={1: {'A': 5, 'B': 3, 'C': 4, 'D': 4},

           2: {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3},

           3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},

           4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},

           5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}

          }

    return rating_data

#——————————————————————————————

#训练和预测数据

rating_data= loadData()

base_svd = SVD(rating_data,F=10)

P,Q = base_svd.train()

print(pd.DataFrame(base_svd.P))

print(pd.DataFrame(base_svd.Q))

print(pd.DataFrame(base_svd.rating_data))

print(base_svd.rating_data[1]['B'])

print(base_svd.predict(1,'B'))

矩阵分解优缺点分析:

1.将评分矩阵化解成用户和物品的隐向量表示,降低了原始数据的稀疏

2.空间复杂度降低,从原本的n^2矩阵 分解成小矩阵

3.矩阵分解成的隐向量和Embedding相似,便于与其他特征进行组合和拼接

4.依然只考虑到了用户和物品的评分矩阵,为考虑用户和物品特征等,

逻辑回归和后续因子分解机模型可解决该问题,

    • SVD用于电影推荐的流程

1.获取用户对电影的真实评分矩阵

2.随机数构建用户评分矩阵Q1,电影评分矩阵R1,用户评分偏差矩阵,电影评分偏差矩阵

3.使用梯度下降算法,开始迭代Q1和R1,每次迭代的步长自行设定,使得Q1和R1不断的向真实评分矩阵和预测评分矩阵误差降低的方向,到达提前设定好的迭代次数,停止迭代

4.根据预测分数的计算公式,计算出目标用户,没有看过的电影的预测评分,

5.对评分排序,评分高电影,推荐给用户

    • SVD与协同过滤的精度,哪一个模型的RMSE评分更低?
  • 6:Slope One
    • Slope One用于电影推荐的流程

slope one 是基于物品对协同过滤推荐算法:基于不同物品间评分差,预测用户对物品的个性化评分

1.计算物品间的评分差值的均值,记为评分偏差

2.根据评分偏差,和用户历史评分,预测用户对未评分物品的评分

3.预测评分排序,取topN对应物品推荐给用户

    • Slope One代码编写

class SlopeOne();

    def loadData():

    items={'A':{1:5,2:3},

           'B':{1:3,2:4,3:2},

           'C':{1:2,3:5}}

    users={1:{'A':5,'B':3,'C':2},

           2:{'A':3,'B':4},

           3:{'B':2,'C':5}}

    return items,users

    def buildAverageDiffs(items,users,averages):

    #遍历每条物品-用户评分数据

    for itemId in items:

        for otherItemId in items:

            average=0.0 #物品间的评分偏差均值

            userRatingPairCount=0 #两件物品均评过分的用户数

            if itemId!=otherItemId: #若无不同的物品项

                for userId in users: #遍历用户-物品评分数

                    userRatings=users[userId] #每条数据为用户对物品的评分

                    #当前物品项在用户的评分数据中,且用户也对其他物品由评分

                    if itemId in userRatings and otherItemId in userRatings:

                        #两件物品均评过分的用户数加1

                        userRatingPairCount+=1

                        #评分偏差为每项当前物品评分-其他物品评分求和

                        average+=(userRatings[otherItemId]-userRatings[itemId])

                averages[(itemId,otherItemId)]=average/userRatingPairCount

#targetUserId:被推荐的用户

#targetItemId:被推荐的物品

def suggestedRating(users,items,averages,targetUserId,targetItemId):

    runningRatingCount=0 #预测评分的分母

    weightedRatingTotal=0.0 #分子

    for i in users[targetUserId]:

        #物品i和物品targetItemId共同评分的用户数

        ratingCount=userWhoRatedBoth(users,i,targetItemId)

        #分子

        weightedRatingTotal+=(users[targetUserId][i]-averages[(targetItemId,i)])\

        *ratingCount

        #分母

        runningRatingCount+=ratingCount

    #返回预测评分

    return weightedRatingTotal/runningRatingCount

# 物品itemId1与itemId2共同有多少用户评分

def userWhoRatedBoth(users,itemId1,itemId2):

    count=0

    #用户-物品评分数据

    for userId in users:

        #用户对物品itemId1与itemId2都评过分则计数加1

        if itemId1 in users[userId] and itemId2 in users[userId]:

            count+=1

    return count

if __name__=='__main__':

    items,users=loadData()

    averages={}

    #计算物品之间的评分差

    buildAverageDiffs(items,users,averages)

    #预测评分:用户2对物品C的评分

    predictRating=suggestedRating(users,items,averages,2,'C')

    • Slope One、SVD、协同过滤的精度,哪一个模型的RMSE评分更低?

  • 7和8:词向量和向量召回基础
    • word2vec基础

Embedding:使用低维稠密的向量“表示”一个对象的相应特征

word2vec :生成对“词”的向量表达,有两种表达模型分别为CBOW 和Skip—gram。

假设:一个长度为T的句子,每个词由相邻的词确定

CBOW模型:输入目标word滑动窗口(:目标词前后各选C个词汇)的词汇,来预测目标词汇

Skip—gram模型:输入目标word,来预测目标word的周边词汇

材料准备:由一组句子组成的语料库,设定滑动窗口的长度(2*C+1)

获取训练样本:将滑动窗口在语料库中,从左向右滑动,每移动一次,窗口中的词组就形成来训练样本。

优化目标:基于极大似然估计,希望所有样本的条件概率之积最大

word2Vec的目标:预测句子中单词的近义词,我们想要的是模型训练好后,得到隐层学习的权重矩阵,可以将该权重用作单词的embeddings。

输入层表达就是输入层到隐层的权重矩阵,输出层表达是隐层到输出层的权重矩阵,在获得输入向量矩阵W后,每一层对应的权重向量,均是普通意义上的“词向量”,假设输入向量10000词组成的one—hot矩阵,隐层维度是300维,则输入层到隐层的权重矩阵为10000*300维。转换成词向量查找表后,每行权重成为对应词汇的Embedding向量。

但正常的word2vec模型的数据存储和计算能力需求极为庞大,正常是很难搞定,为此采用负采样

负采样:比原本需要计算训练样本中所有词的预测误差,负样本只需要对采样的几个样本计算预测误差。

word2vec在推荐系统中的应用:

Item2vec:利用用户的浏览,购买等行为产生的历史行为记录

#word2Vec embedding skip—gram model

#任务:逐个选择邻近单词,给出词汇表中单词被选中的概率

#准备训练数据集,自行创建标记数据来训练word2vec模型

#1.设定滑动窗口的大小,选中一句话,从第一个单词开始作为输入,滑动窗口范围内的单词作为输出

#2.一个单词一个单词开始向后移动,直至这句话结束为止。

#3.创建出非结构化的数据集

#获取word Embedding

#1.按照准备数据集的方法,获取输入的词汇量,按照设定好的想为每个单词创建特定维数的向量来表示对应单词,构建单词矩阵,给出wordEmbeding的架构

#2.输入热编码向量表示的单词,输出将给出词汇表中每个单词在其附近的概率

#在非文本数据上应用word2Vec模型

#使用word2Vec模型对文本顺序性的利用,来得到物品的向量描述

    • gensim训练word2vec,然后对用户完成聚类
    • 7的基础上,使用编码后的用户向量,计算用户相似度。
    • User-CF的过程,通过用户相似度得到电影推荐

#完整的代码可以从这里下载:

#https://github.com/prateekjoshi565/recommendation_system/blob/master/recommender_2.ipynb

import pandas as pd

import numpy as np

import random

from tqdm import tqdm

from gensim.models import Word2Vec

import matplotlib.pyplot as plt

%matplotlib inline

import warnings;

warnings.filterwarnings('ignore')

##pip install gensim

#

#

#导入数据集

#使用一个在线零售数据集,你可以从这个链接下载:

#https://archive.ics.uci.edu/ml/machine-learning-databases/00352/

df = pd.read_excel(r'/Users/wudun/Downloads/OnlineRetail.xlsx')

df.head()

##熟悉数据情况

df.shape

#检测缺失数据

df.isnull().sum()

#数据处理1,因为数据充足,删除所有缺少的行

df.dropna(inplace=True)

#数据处理2,将stockCode转换成String

df["StockCode"] = df['StockCode'].astype(str)

#将用户转换为列表

customers = df['CustomerID'].unique().tolist()

customers = df['CustomerID'].unique().tolist()

#数据处理3,分割数据集

#打乱数据顺序

random.shuffle(customers)

#自定义划分数据集,从数据集中提取90%的消费者

Customers = [customers[i] for i in range(round(0.9 *len(customers)))]

#round 四舍五入

#划分训练集和测试集

train_df = df[df['CustomerID'].isin(Customers)]

valid_df = df[~df['CustomerID'].isin(Customers)]

#数据处理4,获取训练集和测试集合中用户的购买序列

#训练集合中,用户的购买序列

#tqdm 进度条库

purchase_train = [] #定义用户集合

for i in tqdm(train_df['CustomerID'].unique().tolist()):#在用户集合中选取符合条件的用户购买信息

    temp  = train_df[train_df['CustomerID'] == i ]['StockCode'].tolist()#获取每个用户购买商品的代码列表

    purchase_train.append(temp)

#测试集合中,用户的购买序列

purchase_valid  = [] #定义测试集

for i in tqdm(valid_df['CustomerID'].unique().tolist()):

    temp = valid_df[valid_df['CustomerID'] == i]['StockCode'].tolist()

    purchase_valid.append(temp)

#模型建立

    ######################不是很懂

#构建Word2 Embedding

#训练word2vec 模型

model = Word2Vec(window = 10,sg = 1,hs = 0,

negative = 10,# for negative sampling

alpha = 0.03, min_alpha = 0.007,

seed = 14)

'''

sg=1 是 skip-gram 算法,对低频词敏感;默认 sg=0 为 CBOW 算法。

size 是输出词向量的维数,值太小会导致词映射因为冲突而影响结果,值太大则会耗内存并使算法计算变慢,一般值取为100到200之间。

window 是句子中当前词与目标词之间的最大距离,3表示在目标词前看3-b 个词,后面看 b 个词(b 在0-3之间随机)。

min_count 是对词进行过滤,频率小于 min-count 的单词则会被忽视,默认值为5。

negative 和 sample 可根据训练结果进行微调,sample 表示更高频率的词被随机下采样到所设置的阈值,默认值为 1e-3。

hs=1 表示层级 softmax 将会被使用,默认 hs=0 且 negative 不为0,则负采样将会被选择使用。

'''

model.build_vocab(purchase_train,progress_per=200)

model.train(purchase_train,total_examples = model.corpus_count,

epochs=10, report_delay=1)

#1.标点符号出错,2,属性字母填写出错        

#提高模型的内存效率

model.init_sims(replace=True)

#提取模型中词汇表中所有单词的向量,并存储

X = model.wv.key_to_index

#使用UMAP算法来降维

import umap

cluster_embedding = umap.UMAP(n_neighbors=30,min_dist = 0.0, n_components = 2,random_state = 42).fit_transform(X)

#绘图,可视化

plt.figure(figsize = (10,9))

plt.scatter(cluster_embedding[:,0],cluster_embdedding[:,1],s=3,cmap='Spectral')

           #开始推荐商品

#1.创建一个商品和商品描述的字典,以便将商品描述映射到其Id

products = train_df[["StockCode","Description"]]

#2.去重

products.drop_duplicates(inplace = True,subset = 'StockCode',keep="last")

#3.创建一个商品id和商品描述字典

products_dict = products.groupby('StockCode')['Description'].apply(list).to_dict()

def similar_products(v,n =6):

    #为输入向量提取最相似的商品

    ms = model.similar_by_vector(v,topn = n+1)[1:]

    #提取相似产品的名称和相似度评分

    new_ms = []

    for j in ms:

        pair (products_dict[j[0]][0],j[1])

        new_ms,append(pair)

    return new_ms

#预测商品编号为 的推荐商品

similar_products(model['90019A'])

#如果想根据多次购买来推荐商品,取用户迄今为止所有购买的商品向量的平均值,利用结构向量找到类似的商品

#使用该函数,接收商品id,返回100维度的向量,是输入列表商品向量的平均值

def aggregate_vectors(products):

    product_vec = []

    for i in products:

        try:

            product_vec.append(model[i])

        except KeyError:

    return np.mean(product_vec,axis = 0)

  • 9:多路召回实践
    • 3、任务5、任务6、任务7、任务8,总共5个召回模型,进行多路召回。
    • & 多路召回模型的Top10、Top20、Top50的命中率。
;